在開始寫UINavigationBar
以前,瞭解幾個導航欄中用到的知識,將會更有利於理解。ios
首先須要明確UINavigationBar
是能夠脫離UINavigationConroller
單獨做爲控件的。只是UINavigationConroller
建立的 navigationBar 的代理UINavigationBarDelegate
是 navigationConroller 自身。對於代碼或 IB 直接建立的 navigationBar,代理則須要本身指定。swift
相信你們對tintColor
這個東西確定不會陌生,這裏就再也不累述,只記錄一下本人以前的一個疑惑:UILabel
爲何不受tintColor
的影響?有位大佬在這裏比較詳細的講解了,我就大概記錄下本身的理解:app
Apple
避免在可交互元素上使用邊框和漸變,取而代之使用tintColor
,那麼tintColor
的核心思想就是區分元素是否能夠響應觸摸。顯而易見的,UILabel
是不可交互元素,即使你設置它的tintColor
也不會被繪製。async
系統有三個關於高斯模糊效果的類,父類:UIVisualEffect
,兩個子類:UIBlurEffect
和UIVibrancyEffect
。flex
UIVisualEffectView
就是展現這些效果的視圖,文檔裏說:ui
Depending on the desired effect, the effect may affect content layered behind the view or content added to the visual effect view’s contentView.spa
對於UIVisualEffectView
,根據想要的 effect,3d
UIBlurEffect
只是簡單的給UIVisualEffectView
後面的視圖添加高斯模糊效果,對於添加到UIVisualEffectView
的contentView
中的視圖則不會產生模糊效果。代理
UIVibrancyEffect
不會給UIVisualEffectView
後面的視圖產生模糊,只會使添加到contentView
中的視圖更加生動。code
對於UIBlurEffect
的UIVisualEffectView
,若它的contentView
中又包含了一個UIVibrancyEffect
的UIVisualEffectView
。則顯示效果又有模糊效果,又有生動效果。
lazy var blurContainVibrancyView: UIVisualEffectView = {
let vibrancyEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .light)))
let label = self.creatLabel(withText: "而卒莫消長也")
label.center = vibrancyEffectView.contentView.center
vibrancyEffectView.contentView.addSubview(label)
let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
blurEffectView.contentView.addSubview(vibrancyEffectView)
vibrancyEffectView.frame = blurEffectView.frame
vibrancyEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
return blurEffectView
}()
複製代碼
文檔指出不要給UIVisualEffectView
或者它的父視圖設置小於 1 的alpha
值,不然 effect 可能顯示不正確,或者根本不顯示。可是能夠設置contentView
子視圖的alpha
(在已經嘗試過的實際運用中的時候,設置 UIVisualEffectView
的alpha
小於 1 時,Xcode 會報警告可是透明和模糊效果都存在。設置它父視圖的alpha
小於 1 則沒有警告可是隻有透明效果)。
enum UIBarStyle : Int {
case`default`
case black
}
複製代碼
默認白底黑字,black 樣式爲黑底白字。且這兩種樣式都默認半透明(isTranslucent = true
)。
barTintColor
:用來導航欄背景色,不要使用backgroundColor
。
backgroundColor
(藍色),顏色顯示不正確:backgroundColor
,顏色徹底不顯示:tintColor
:影響 bar 的子視圖顏色。
titleTextAttributes
:常見的NSAttributedString
設置。setTitleVerticalPositionAdjustment(CGFloat, for: UIBarMetrics)
:標題豎直方向偏移量。影響navigationBar
的半透明效果,默認爲true
。
isTranslucent
的navigationBar
,若是背景圖alpha < 1
,則isTranslucent = true
。反之爲false
。isTranslucent = true
的,若是背景圖爲不透明,則會爲背景圖會被添加小於 1 的系統定義的alpha
。isTranslucent = false
的,若是背景圖alpha < 1
,會根據barStyle
或barTintColor
爲該圖片添加一個相應顏色的不透明背景。只有在設置過背景圖片的狀況下,陰影圖片纔會生效。單獨設置陰影圖片沒有效果。
shadowImage
的位置其實是超出了它的父視圖的,設置navigationBar.clipsToBounds = true
也能夠隱藏。
假設isTranslucent = true
。
navigationBar
的子視圖中將會包含一個visualEffectView
用來產生模糊效果。navigationBar
的子視圖中將不會包含visualEffectView
,而是直接生成一個半透明的背景圖。public enum UIBarPosition : Int {
case any // 未指明的
case bottom // 指定 bar 在父視圖的底部,各類陰影都會被繪製在 bar 頂部
case top // 指定 bar 在父視圖的頂部,各類陰影都會被繪製在 bar 底部
case topAttached // 指定 bar 和父視圖都在屏幕的頂部,而且 bar 的背景會穿透狀態欄
}
複製代碼
barPosition
實際上是協議UIBarPositioning
中定義的屬性,UINavigationBar
默認遵照了該協議,值爲.top
。
開篇就說到,UINavigationConroller
建立的 navigationBar,代理爲 navigationConroller 自身。其默認實現爲.topAttached
。
若是本身建立一個 navigationBar 並將其添加到當前控制器視圖中,指定代理爲當前控制器。並實
現UINavigationBarDelegate
:
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
複製代碼
能夠獲得和原生一樣的效果(圖中系統 iOS 10,高度爲自定義,iOS 11 顯示效果不同喲):
在項目中時常有點擊導航欄返回按鈕,彈出確認返回的提示,此時就須要攔截返回事件。
自定義一個NavigationBarShouldPopProtocol
將是否能夠 pop 的控制權限交給當前控制器,再修改UINavigationController
的默認實現,每次都詢問topViewController
是否能夠 pop。且咱們能夠在shouldPopWhenClickBackButton
方法中作一些額外操做(好比返回false
,彈出提示框)。
protocol NavigationBarShouldPopProtocol {
func shouldPopWhenClickBackButton() -> Bool
}
// 點擊 navigationBar 的 backButton 是否 pop,默認爲 true
extension UIViewController: NavigationBarShouldPopProtocol {
@objc func shouldPopWhenClickBackButton() -> Bool {
return true
}
}
複製代碼
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
guard let items = navigationBar.items else {
return false
}
if viewControllers.count < items.count {
return true
}
var shouldPop = true
if let controller = topViewController, controller.responds(to: #selector(UIViewController.shouldPopWhenClickBackButton)) {
// 詢問是否能夠 pop
shouldPop = controller.shouldPopWhenClickBackButton()
}
if shouldPop {
DispatchQueue.main.async {
self.popViewController(animated: true)
}
} else {
for view in navigationBar.subviews {
if view.alpha > 0 && view.alpha < 1 {
view.alpha = 1
}
}
}
return false
}
}
複製代碼