需求
在某界面里,navigation bar 的主體透明(isTranslucent 為 false),但 navigation items 不透明(如 backButtonItem, rightItems)。
在 iOS 11 以前的做法
結(jié)合以下代碼:
extension UINavigationBar {
// Actual type: _UIBarBackground
var background: UIView {
return subviews[0]
}
}
直接在 viewWillAppear(_:) 和 viewWillDisappear(_:) 里加動(dòng)畫(huà),改上述 background 屬性的 alpha 。
問(wèn)題
在 iOS 11 上用這方法無(wú)法達(dá)到效果,navigation bar 會(huì)變回白色或黑色。
失敗的解決方案
- 上述方案
- 使用
navigationBar.setBackgroundImage(_:forBarMetrics:)加navigationBar.shadowImage的方案
分析
基于上述 extension 的前提下,使用 KVO 找出是什么調(diào)用使得 background 的 alpha 改變。
- 在
viewDidLoad()中加入
navigationController?.navigationBar.background.addObserver(self, forKeyPath: "alpha", options: .new, context: nil)
- 實(shí)現(xiàn)監(jiān)聽(tīng)方法
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "alpha" {
// set a breakpoint here
print("navigationBar.background.alpha = \(navigationController!.navigationBar.background.alpha)")
}
}
- 在上述位置打個(gè)斷點(diǎn)觀察,然后結(jié)果是:
system tracks.png
結(jié)果是系統(tǒng)會(huì)在一個(gè)私有方法中更新background的透明度。
最終解決方案
在系統(tǒng)方法改動(dòng) alpha 前,跟我們?cè)O(shè)置過(guò)的 alpha 比較一下,如果我們?cè)O(shè)過(guò) alpha 為0的,就不許系統(tǒng)設(shè) alpha 為1。這就要我們使用 Method Swizzling 替換 alpha 的 setter 方法了。
需要說(shuō)明的是,由于動(dòng)的是系統(tǒng)私有類(_UIBarBackground),下面的這些 extension 都只好拿 UIView 開(kāi)刀了,然后在必要的地方做一些類型判斷。如果你有更好的實(shí)現(xiàn)方式,請(qǐng)留言。
extension UIView {
private struct Key {
static var loyalAlpha = "loyalAlpha"
}
/// A highly loyal ALPHA who won't be changed by system calls.
var loyalAlpha: CGFloat? {
get {
return objc_getAssociatedObject(self, &Key.loyalAlpha) as? CGFloat
}
set {
objc_setAssociatedObject(self, &Key.loyalAlpha, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
if let value = newValue {
alpha = value
}
}
}
}
extension NSObject {
/// Exchange two selectors
///
/// - Parameters:
/// - originalSelector: The original selector
/// - swizzledSelector: A new selector
class func exchangeImplementations(originalSelector: Selector, swizzledSelector: Selector) {
guard
let originalMethod = class_getInstanceMethod(self, originalSelector),
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
else {
print("Error: Unable to exchange method implemenation!!")
return
}
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
extension UIView {
class func applySwizzledMethods() {
exchangeSetAlpha
}
private static let exchangeSetAlpha: Void = {
let os = #selector(setter: alpha)
let ss = #selector(swizzledSetAlpha(_:))
exchangeImplementations(originalSelector: os, swizzledSelector: ss)
print("SWIZZLE WARNING: 'exchangeSetAlpha' has been activated!")
}()
@objc private func swizzledSetAlpha(_ alpha: CGFloat) {
if type(of: self) == NSClassFromString("_UIBarBackground") {
if let loyalAlpha = loyalAlpha, loyalAlpha != alpha {
return
}
}
swizzledSetAlpha(alpha)
}
}
所以我實(shí)現(xiàn)的是一個(gè)“非常忠誠(chéng)的 alpha”,只聽(tīng)我的,不聽(tīng)系統(tǒng)的。但我們要小心使用,不可傷及無(wú)辜的 UIView 對(duì)象。
用法嘛,還是在 viewWillAppear(_:) 和 viewWillDisappear(_:) 里加動(dòng)畫(huà),但改 background 的是 loyalAlpha 。
extension UINavigationBar {
func animateBackground(show: Bool) {
UIView.animate(withDuration: 0.25) {
self.background.loyalAlpha = show ? 1 : 0
}
if !show {
self.background.loyalAlpha = nil
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.animateBackground(show: false)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.animateBackground(show: true)
}
最后不要忘記就是,要在 application(_:didFinishLaunchingWithOptions:) 中啟動(dòng)整個(gè) Swizzling 黑魔法。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UIView.applySwizzledMethods()
return true
}
最終效果

