做爲 iOS 開發者,不免要和導航欄打交道,一般呢,像微信這樣優秀且友好的應用,全局使用系統導航欄交互效果就很是好了。然而爲了更進一步,老是須要更深刻地定製化導航欄,包括卻不止像(半)透明
、滑動漸變
等交互效果,以及標題
、顏色
、偏移
還有對應狀態欄(StatusBar)的變化。ios
閱讀參考了諸多開發者的經驗分享,併發起了一個騰訊投票以瞭解大多數人是傾向於採用什麼樣的方式來處理導航欄的問題,因而決定採用系統導航欄+自定義導航欄
共用的方式來處理。swift
開始折騰以前,先簡單說下我對這三種方式的理解。微信
以添加Catagory(OC)
或Extension(Swift)
、重載系統方法
等形式,拿到並修改系統導航欄的View,或添加所須要的View來實現本身定製化的需求。併發
這種方式可參考這幾篇中文分享,寫得很是詳細:ide
隱藏系統導航欄,各頁面採用自定義導航欄進行需求定製。動畫
通常來講,一個優秀且友好的應用,多會遵循蘋果官方的設計規範,故而絕大多數頁面仍是可以方便地採用系統導航欄進行處理,此時,部分頁面出彩的交互設計,則能夠暫時隱藏系統導航欄,採用自定義導航欄進行實現。ui
總的來講,三種方式各有優缺,主要仍是按照不一樣的需求採用不一樣的方案,如果導航欄真的須要水波爛漫的交互效果,側滑返回的時候還要有個小船劃回去,這若非要挑戰經過修改系統導航欄的方式實現,費勁踩坑估計在所不免。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)
}
}複製代碼
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項目。如果但願繼續瞭解修改系統導航欄的實現方式,可參考文中所說起的幾篇分享,強烈推薦。