簡(jiǎn)介
Bartinter 是一個(gè)關(guān)于 StatusBar 的庫,它的功能很簡(jiǎn)單也很實(shí)用:
Dynamically changes status bar style depending on content behind it
使用也簡(jiǎn)單:
- Set "View controller-based status bar appearance" (UIViewControllerBasedStatusBarAppearance) to YES in your Info.plist.
- Set ViewController's
updatesStatusBarAppearanceAutomatically = true
原理
有兩個(gè)點(diǎn)
- 一個(gè)是在需要的時(shí)候,計(jì)算狀態(tài)欄的亮度
- 一個(gè)是利用 AOP 把上面的流程自動(dòng)化
計(jì)算狀態(tài)欄的亮度
注意 Bartinter 是一個(gè)繼承自 UIViewController 的類:public final class Bartinter: UIViewController,它以 childViewController 的形式獲得了父 VC 的生命周期狀態(tài)。
@objc public func refreshStatusBarStyle() 是更新狀態(tài)欄的核心函數(shù)。
其中,private func calculateStatusBarAreaAvgLuminance(_ completion: @escaping (CGFloat) -> Void) 方法,獲取父 VC 的 CALayer 和圖形上下文,計(jì)算狀態(tài)欄平均亮度,以決定 statusBarStyle。
注意這里有一個(gè)很貼心的細(xì)節(jié)是 antiFlickRange,用來防止亮度變更導(dǎo)致的狀態(tài)欄反復(fù)變化。如果沒有這一個(gè)細(xì)節(jié),雖然功能實(shí)現(xiàn)了,但是整體體驗(yàn)勢(shì)必要降低好幾個(gè)檔次。
@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
}
}
}
流程自動(dòng)化
利用 AOP 把上面的流程自動(dòng)化。通過 hook 了UIViewController.childForStatusBarStyle,來返回 statusBarStyle。
設(shè)置則是通過 @IBInspectable var updatesStatusBarAppearanceAutomatically: Bool 這個(gè)入口,為 VC 附上一個(gè) Bartinter 的實(shí)例.
雖然示例里說手動(dòng)觸發(fā)舉的??是func scrollViewDidScroll(_ scrollView: UIScrollView),但實(shí)際上 AOP 的時(shí)候并沒有監(jiān)聽這個(gè)方法。
它監(jiān)聽的方法是:
public override func viewWillAppear(_ animated: Bool) // 通過 childViewController
public override func viewDidLayoutSubviews() // 通過 childViewController
UIView.layoutSubviews // 通過 AOP 堅(jiān)挺了 parent.view
這里還有一個(gè)很 tricky 的地方,在于 Bartinter 的 static func swizzleIfNeeded() 方法,因?yàn)槔锩娌]有做多線程保護(hù),所以第一想法就是擔(dān)心會(huì)產(chǎn)生多次 AOP 的問題。于是就著測(cè)試了一下,結(jié)果發(fā)現(xiàn)這個(gè)擔(dān)憂通過主線程得到了保護(hù):
Bartinter 的 static func swizzleIfNeeded() 方法只在初始化的時(shí)候調(diào)用,而如果我們?cè)诋惒骄€程去調(diào)用 init 方法,會(huì)觸發(fā) Apple 的錯(cuò)誤:“-[UIViewController initWithNibName:bundle:] must be used from main thread only”。通過主線程限制而規(guī)避了多線程競(jìng)爭(zhēng)問題。
并且它是 internal 的,外部不可訪問,防止了開發(fā)者的濫用。
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
這個(gè) Throttler 類挺有意思,可以直接用。Throttle 作為一個(gè)很實(shí)用的功能,在 Rx 里面也有集成。
這個(gè) Throttler 采用的是首次立即實(shí)行,后續(xù)延遲執(zhí)行的方案,保證一個(gè)maxInterval 內(nèi)最多只會(huì)執(zhí)行一次。在一直調(diào)用的情況下最壞下次執(zhí)行需要間隔 (2 * maxInterval - 1最小時(shí)間單位)。