簡介
Bartinter 是一個關(guān)于 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
原理
有兩個點
- 一個是在需要的時候,計算狀態(tài)欄的亮度
- 一個是利用 AOP 把上面的流程自動化
計算狀態(tài)欄的亮度
注意 Bartinter 是一個繼承自 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 和圖形上下文臣镣,計算狀態(tài)欄平均亮度辅愿,以決定 statusBarStyle。
注意這里有一個很貼心的細(xì)節(jié)是 antiFlickRange
忆某,用來防止亮度變更導(dǎo)致的狀態(tài)欄反復(fù)變化点待。如果沒有這一個細(xì)節(jié),雖然功能實現(xiàn)了弃舒,但是整體體驗勢必要降低好幾個檔次癞埠。
@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燕差。
設(shè)置則是通過 @IBInspectable var updatesStatusBarAppearanceAutomatically: Bool
這個入口,為 VC 附上一個 Bartinter
的實例.
雖然示例里說手動觸發(fā)舉的??是func scrollViewDidScroll(_ scrollView: UIScrollView)
坝冕,但實際上 AOP 的時候并沒有監(jiān)聽這個方法徒探。
它監(jiān)聽的方法是:
public override func viewWillAppear(_ animated: Bool) // 通過 childViewController
public override func viewDidLayoutSubviews() // 通過 childViewController
UIView.layoutSubviews // 通過 AOP 堅挺了 parent.view
這里還有一個很 tricky 的地方,在于 Bartinter 的 static func swizzleIfNeeded()
方法喂窟,因為里面并沒有做多線程保護(hù)测暗,所以第一想法就是擔(dān)心會產(chǎn)生多次 AOP 的問題。于是就著測試了一下磨澡,結(jié)果發(fā)現(xiàn)這個擔(dān)憂通過主線程得到了保護(hù):
Bartinter 的 static func swizzleIfNeeded()
方法只在初始化的時候調(diào)用碗啄,而如果我們在異步線程去調(diào)用 init 方法,會觸發(fā) Apple 的錯誤:“-[UIViewController initWithNibName:bundle:] must be used from main thread only”稳摄。通過主線程限制而規(guī)避了多線程競爭問題稚字。
并且它是 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
這個 Throttler
類挺有意思,可以直接用仗阅。Throttle 作為一個很實用的功能昌讲,在 Rx 里面也有集成。
這個 Throttler 采用的是首次立即實行减噪,后續(xù)延遲執(zhí)行的方案短绸,保證一個maxInterval 內(nèi)最多只會執(zhí)行一次。在一直調(diào)用的情況下最壞下次執(zhí)行需要間隔 (2 * maxInterval - 1最小時間單位)筹裕。