簡介
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最小時間單位)。