iOS - 實(shí)現(xiàn) navigationBar 透明底層 for iOS 11

需求

在某界面里,navigation bar 的主體透明(isTranslucentfalse),但 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)用使得 backgroundalpha 改變。

  1. viewDidLoad() 中加入
navigationController?.navigationBar.background.addObserver(self, forKeyPath: "alpha", options: .new, context: nil)
  1. 實(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)")
    }
}
  1. 在上述位置打個(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
}

最終效果

demo.gif

Give Me a LIKE ??

https://github.com/Hesse-Huang/LoyalAlphaDemo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容