美文网首页
iOS - 实现 navigationBar 透明底层 for

iOS - 实现 navigationBar 透明底层 for

作者: Hesse_Huang | 来源:发表于2018-02-05 19:09 被阅读262次

    需求

    在某界面里,navigation bar 的主体透明(isTranslucentfalse),但 navigation items 不透明(如 backButtonItem, rightItems)。

    在 iOS 11 以前的做法

    结合以下代码:

    extension UINavigationBar { 
        // Actual type: _UIBarBackground  
        var background: UIView { 
            return subviews[0]
        }
    }
    

    直接在 viewWillAppear(_:)viewWillDisappear(_:) 里加动画,改上述 background 属性的 alpha

    问题

    在 iOS 11 上用这方法无法达到效果,navigation bar 会变回白色或黑色。

    失败的解决方案

    • 上述方案
    • 使用 navigationBar.setBackgroundImage(_:forBarMetrics:)navigationBar.shadowImage 的方案

    分析

    基于上述 extension 的前提下,使用 KVO 找出是什么调用使得 backgroundalpha 改变。

    1. viewDidLoad() 中加入
    navigationController?.navigationBar.background.addObserver(self, forKeyPath: "alpha", options: .new, context: nil)
    
    1. 实现监听方法
    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. 在上述位置打个断点观察,然后结果是:
      system tracks.png
      结果是系统会在一个私有方法中更新 background 的透明度。

    最终解决方案

    在系统方法改动 alpha 前,跟我们设置过的 alpha 比较一下,如果我们设过 alpha 为0的,就不许系统设 alpha 为1。这就要我们使用 Method Swizzling 替换 alpha 的 setter 方法了。
    需要说明的是,由于动的是系统私有类(_UIBarBackground),下面的这些 extension 都只好拿 UIView 开刀了,然后在必要的地方做一些类型判断。如果你有更好的实现方式,请留言。

    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)
        }
    }
    

    所以我实现的是一个“非常忠诚的 alpha”,只听我的,不听系统的。但我们要小心使用,不可伤及无辜的 UIView 对象。
    用法嘛,还是在 viewWillAppear(_:)viewWillDisappear(_:) 里加动画,但改 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:) 中启动整个 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

    相关文章

      网友评论

          本文标题:iOS - 实现 navigationBar 透明底层 for

          本文链接:https://www.haomeiwen.com/subject/nsllzxtx.html