UINavigationBar簡單解析

額外知識

在開始寫UINavigationBar以前,瞭解幾個導航欄中用到的知識,將會更有利於理解。ios

可單獨使用

首先須要明確UINavigationBar是能夠脫離UINavigationConroller單獨做爲控件的。只是UINavigationConroller建立的 navigationBar 的代理UINavigationBarDelegate是 navigationConroller 自身。對於代碼或 IB 直接建立的 navigationBar,代理則須要本身指定。swift

TintColor

相信你們對tintColor這個東西確定不會陌生,這裏就再也不累述,只記錄一下本人以前的一個疑惑:UILabel爲何不受tintColor的影響?有位大佬在這裏比較詳細的講解了,我就大概記錄下本身的理解:app

Apple避免在可交互元素上使用邊框和漸變,取而代之使用tintColor,那麼tintColor的核心思想就是區分元素是否能夠響應觸摸。顯而易見的,UILabel是不可交互元素,即使你設置它的tintColor也不會被繪製。async

VisualEffect

系統有三個關於高斯模糊效果的類,父類:UIVisualEffect,兩個子類:UIBlurEffectUIVibrancyEffectflex

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後面的視圖添加高斯模糊效果,對於添加到UIVisualEffectViewcontentView中的視圖則不會產生模糊效果。代理

  • UIVibrancyEffect不會給UIVisualEffectView後面的視圖產生模糊,只會使添加到contentView中的視圖更加生動。code

  • 對於UIBlurEffectUIVisualEffectView,若它的contentView中又包含了一個UIVibrancyEffectUIVisualEffectView。則顯示效果又有模糊效果,又有生動效果。

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
}()
複製代碼

blurContainVibrancyView
blurContainVibrancyView

文檔指出不要給UIVisualEffectView或者它的父視圖設置小於 1 的alpha值,不然 effect 可能顯示不正確,或者根本不顯示。可是能夠設置contentView子視圖的alpha(在已經嘗試過的實際運用中的時候,設置 UIVisualEffectViewalpha小於 1 時,Xcode 會報警告可是透明和模糊效果都存在。設置它父視圖的alpha小於 1 則沒有警告可是隻有透明效果)。

外觀

導航欄樣式 barStyle

enum UIBarStyle : Int {    
    case`default`
    case black
}
複製代碼

默認白底黑字,black 樣式爲黑底白字。且這兩種樣式都默認半透明(isTranslucent = true)。

barTintColor、tintColor

barTintColor:用來導航欄背景色,不要使用backgroundColor

  • 對於半透明導航欄,設置backgroundColor(藍色),顏色顯示不正確:

半透明設置背景色
半透明設置背景色

  • 對於不透明導航欄,設置backgroundColor,顏色徹底不顯示:

不透明設置背景色
不透明設置背景色

tintColor:影響 bar 的子視圖顏色。

標題文字樣式

  • titleTextAttributes:常見的NSAttributedString設置。
  • setTitleVerticalPositionAdjustment(CGFloat, for: UIBarMetrics):標題豎直方向偏移量。

isTranslucent

影響navigationBar的半透明效果,默認爲true

  • 對於沒有明確設置isTranslucentnavigationBar,若是背景圖alpha < 1,則isTranslucent = true。反之爲false
  • 對於明確設置isTranslucent = true的,若是背景圖爲不透明,則會爲背景圖會被添加小於 1 的系統定義的alpha
  • 對於明確設置isTranslucent = false的,若是背景圖alpha < 1,會根據barStylebarTintColor爲該圖片添加一個相應顏色的不透明背景。

背景圖和陰影圖

只有在設置過背景圖片的狀況下,陰影圖片纔會生效。單獨設置陰影圖片沒有效果。

shadowImage的位置其實是超出了它的父視圖的,設置navigationBar.clipsToBounds = true也能夠隱藏。

假設isTranslucent = true

  • 若是沒有背景圖片,navigationBar的子視圖中將會包含一個visualEffectView用來產生模糊效果。

默認視圖層級
默認視圖層級

  • 若是設置了背景圖片,navigationBar的子視圖中將不會包含visualEffectView,而是直接生成一個半透明的背景圖。

設置背景圖後的視圖層級
設置背景圖後的視圖層級

代理

導航欄位置 barPosition

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 顯示效果不同喲):

本身建立UINavigationBar
本身建立UINavigationBar

攔截返回操做

在項目中時常有點擊導航欄返回按鈕,彈出確認返回的提示,此時就須要攔截返回事件。

自定義一個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
    }
}
複製代碼
相關文章
相關標籤/搜索