简介
Bartinter 是一个关于 StatusBar 的库,它的功能很简单也很实用:
Dynamically changes status bar style depending on content behind it
使用也简单:
- Set "View controller-based status bar appearance" (UIViewControllerBasedStatusBarAppearance) to YES in your Info.plist.
- Set ViewController's
updatesStatusBarAppearanceAutomatically = true
原理
有两个点
- 一个是在需要的时候,计算状态栏的亮度
- 一个是利用 AOP 把上面的流程自动化
计算状态栏的亮度
注意 Bartinter 是一个继承自 UIViewController 的类:public final class Bartinter: UIViewController
,它以 childViewController 的形式获得了父 VC 的生命周期状态。
@objc public func refreshStatusBarStyle()
是更新状态栏的核心函数。
其中,private func calculateStatusBarAreaAvgLuminance(_ completion: @escaping (CGFloat) -> Void)
方法,获取父 VC 的 CALayer 和图形上下文,计算状态栏平均亮度,以决定 statusBarStyle。
注意这里有一个很贴心的细节是 antiFlickRange
,用来防止亮度变更导致的状态栏反复变化。如果没有这一个细节,虽然功能实现了,但是整体体验势必要降低好几个档次。
@objc public func refreshStatusBarStyle() {
calculateStatusBarAreaAvgLuminance { [weak self] avgLuminance in
guard let strongSelf = self else { return }
let antiFlick = strongSelf.configuration.antiFlickRange / 2
if avgLuminance <= strongSelf.configuration.midPoint - antiFlick {
strongSelf.statusBarStyle = .lightContent
} else if avgLuminance >= strongSelf.configuration.midPoint + antiFlick {
strongSelf.statusBarStyle = .default
}
}
}
流程自动化
利用 AOP 把上面的流程自动化。通过 hook 了UIViewController.childForStatusBarStyle
,来返回 statusBarStyle。
设置则是通过 @IBInspectable var updatesStatusBarAppearanceAutomatically: Bool
这个入口,为 VC 附上一个 Bartinter
的实例.
虽然示例里说手动触发举的是func scrollViewDidScroll(_ scrollView: UIScrollView)
,但实际上 AOP 的时候并没有监听这个方法。
它监听的方法是:
public override func viewWillAppear(_ animated: Bool) // 通过 childViewController
public override func viewDidLayoutSubviews() // 通过 childViewController
UIView.layoutSubviews // 通过 AOP 坚挺了 parent.view
这里还有一个很 tricky 的地方,在于 Bartinter 的 static func swizzleIfNeeded()
方法,因为里面并没有做多线程保护,所以第一想法就是担心会产生多次 AOP 的问题。于是就着测试了一下,结果发现这个担忧通过主线程得到了保护:
Bartinter 的 static func swizzleIfNeeded()
方法只在初始化的时候调用,而如果我们在异步线程去调用 init 方法,会触发 Apple 的错误:“-[UIViewController initWithNibName:bundle:] must be used from main thread only”。通过主线程限制而规避了多线程竞争问题。
并且它是 internal 的,外部不可访问,防止了开发者的滥用。
static func swizzleIfNeeded() {
guard isSwizzlingEnabled && !isSwizzlingPerformed else { return }
UIViewController.setupChildViewControllerForStatusBarStyleSwizzling()
UIView.setupSetNeedsLayoutSwizzling()
isSwizzlingPerformed = true
}
public init(_ configuration: Configuration = Configuration()) {
self.configuration = configuration
Bartinter.swizzleIfNeeded()
super.init(nibName: nil, bundle: nil)
}
Throttler
这个 Throttler
类挺有意思,可以直接用。Throttle 作为一个很实用的功能,在 Rx 里面也有集成。
这个 Throttler 采用的是首次立即实行,后续延迟执行的方案,保证一个maxInterval 内最多只会执行一次。在一直调用的情况下最坏下次执行需要间隔 (2 * maxInterval - 1最小时间单位)。