iOS Navigation Bar 導航欄折騰記 (Swift&OC)

做爲 iOS 開發者,不免要和導航欄打交道,一般呢,像微信這樣優秀且友好的應用,全局使用系統導航欄交互效果就很是好了。然而爲了更進一步,老是須要更深刻地定製化導航欄,包括卻不止像(半)透明滑動漸變等交互效果,以及標題顏色偏移還有對應狀態欄(StatusBar)的變化。ios

閱讀參考了諸多開發者的經驗分享,併發起了一個騰訊投票以瞭解大多數人是傾向於採用什麼樣的方式來處理導航欄的問題,因而決定採用系統導航欄+自定義導航欄共用的方式來處理。swift

發起的iOS導航欄自定義實現方式偏好投票
發起的iOS導航欄自定義實現方式偏好投票

開始折騰以前,先簡單說下我對這三種方式的理解。微信

修改系統導航欄

以添加Catagory(OC)Extension(Swift)重載系統方法等形式,拿到並修改系統導航欄的View,或添加所須要的View來實現本身定製化的需求。併發

優勢:

  1. 實現好後,各控制器定製起來調用方便,每每一兩行代碼就能夠了。
  2. 可以保留側滑返回的導航欄過渡效果(這個依需求而定,也並徹底算優勢)

缺點:

  1. 實現方式複雜,涉及系統屬性方法的修改,容易趕上各類未知的坑

這種方式可參考這幾篇中文分享,寫得很是詳細:ide

徹底使用自定義導航欄

隱藏系統導航欄,各頁面採用自定義導航欄進行需求定製。動畫

優勢

  1. 避免系統導航欄存在的各類未知坑
  2. 實現效果可高度自定義,高興的話能夠設計成波浪形,還帶動畫交互的那種
  3. 通常有些應用採用底部導航欄的設計,基本都是徹底使用自定義導航欄實現

缺點

  1. 通常沒有系統導航欄的側滑過渡效果,可參考手淘。(不算徹底意義上的缺點)
  2. 依據不一樣的需求和實現方式,工做量可能較大
  3. 側滑返回手勢、滑動隱藏、觸控隱藏等一些系統交互需自行實現
  4. 須要額外處理系統導航欄可以自動處理的in call等系統響應

系統導航欄與自定義導航欄共用

通常來講,一個優秀且友好的應用,多會遵循蘋果官方的設計規範,故而絕大多數頁面仍是可以方便地採用系統導航欄進行處理,此時,部分頁面出彩的交互設計,則能夠暫時隱藏系統導航欄,採用自定義導航欄進行實現。ui

優勢

  1. 避免修改系統導航欄可能遇到的坑
  2. 僅部分頁面針對性採用自定義導航欄,工做量相對可控
  3. 採用系統導航欄的頁面之間保留側滑過渡效果

缺點

  1. 如果須要自定義導航欄的頁面較多,工做增量較大
  2. 自定義導航欄頁面的側滑返回等效果須要額外處理

小結

總的來講,三種方式各有優缺,主要仍是按照不一樣的需求採用不一樣的方案,如果導航欄真的須要水波爛漫的交互效果,側滑返回的時候還要有個小船劃回去,這若非要挑戰經過修改系統導航欄的方式實現,費勁踩坑估計在所不免。spa

開始折騰 - [系統導航欄+自定義導航欄方案]

添加自定義導航欄

fileprivate lazy var customNavigationItem: UINavigationItem = UINavigationItem(title: "Profile")
fileprivate lazy var customNavigationBar: UINavigationBar = {

        let bar = UINavigationBar(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 64))

        bar.tintColor = UIColor.white
        bar.tintAdjustmentMode = .normal
        bar.alpha = 0
        bar.setItems([self.customNavigationItem], animated: false)

        bar.backgroundColor = UIColor.clear
        bar.barStyle = UIBarStyle.blackTranslucent
        bar.isTranslucent = true
        bar.shadowImage = UIImage()
        bar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)

        let textAttributes = [
            NSForegroundColorAttributeName: UIColor.white,
            NSFontAttributeName: UIFont.systemFont(ofSize: 16)
        ]

        bar.titleTextAttributes = textAttributes

        return bar
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(customNavigationBar)

        prepareData()

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        navigationController?.setNavigationBarHidden(true, animated: true)

        // 便於自定義BarButtomItem
        setBackButton()
        customNavigationBar.alpha = 1.0
    }

    func setBackButton() {
        let backBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "back_white"), style: .plain, target: self, action: #selector(DataProjectDetailViewController.back(_:)))

        self.customNavigationItem.leftBarButtonItem = backBarButtonItem
    }

    @objc fileprivate func back(_ sender: AnyObject) {
        if let presentingViewController = presentingViewController {
            presentingViewController.dismiss(animated: true, completion: nil)
        } else {
            _ = navigationController?.popViewController(animated: true)
        }
    }複製代碼

如果須要對導航欄進行滑動動畫或漸變等處理,則在ScrollView代理方法中對自定義導航欄的屬性進行修改。.net

須要額外強調的是,最好在BaseViewController中對系統導航欄的一些屬性作統一初始化處理,以期全部的控制器達到指望的統一效果,以免自定義頁面對系統導航欄的隱藏等修改影響到其它頁面的系統導航欄。設計

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        guard let navigationController = navigationController else {
            return
        }

        // 僅處理導航欄隱藏後從新顯示,可在此作更多導航欄的統一效果處理
        if navigationController.isNavigationBarHidden {
            navigationController.setNavigationBarHidden(false, animated: animated)
        }
    }複製代碼

處理StatusBar狀態欄樣式

override var preferredStatusBarStyle : UIStatusBarStyle {
        return UIStatusBarStyle.lightContent
    }複製代碼

處理邊緣側滑返回

重點!敲黑板、敲黑板了。處理邊緣側滑返回,須要接管實現導航控制器的邊緣側滑返回交互手勢代理。好在全部的導航控制器來繼承了BaseNavigationController,於是能夠在基類進行統一處理。

class BaseNavigationController: UINavigationController {
    override func setNavigationBarHidden(_ hidden: Bool, animated: Bool) {
        super.setNavigationBarHidden(hidden, animated: animated)

        // 接管導航控制器的邊緣側滑返回交互手勢代理
        interactivePopGestureRecognizer?.delegate = self
    }
}

extension BaseNavigationController: UIGestureRecognizerDelegate {
    // 讓邊緣側滑手勢在合適的狀況下生效
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if (self.viewControllers.count > 1) {
            return true;
        }
        return false;
    }

    // 容許同時響應多個手勢
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }

    // 避免響應邊緣側滑返回手勢時,當前控制器中的ScrollView跟着滑動
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return gestureRecognizer.isKind(of: UIScreenEdgePanGestureRecognizer.self)
    }

}複製代碼

這樣就經過自定義添加方式實現了導航欄的定製化,其餘頁面則繼續愉快使用系統導航欄便可。以上就是全部自定義導航欄須要的核心代碼了,故沒有另外的Demo項目。如果但願繼續瞭解修改系統導航欄的實現方式,可參考文中所說起的幾篇分享,強烈推薦。

因此你偏好哪一種方式呢?

微信掃一掃,選擇屬於你的陣營吧!
微信掃一掃,選擇屬於你的陣營吧!

博客原文連接

相關文章
相關標籤/搜索