一、前言
上篇主儡,我們仿了京東的底部導航欄奖唯,顯示了5個頁面(UIViewController),它的作用你可以理解為頁面之間的切換糜值,每個頁面都屬于一級頁面丰捷;而一級頁面一般只是一些吸引用戶的的主要流量入口,實際的功能頁面都是通過這些一級頁面的入口導航到下一級或是更深一級的頁面(從用戶角度來看寂汇,核心重要的頁面最多不超過三級病往,否則層級太深,用戶可能就沒興趣繼續(xù)點下去了骄瓣,除了『確認訂單』和『支付』頁面)停巷。
N級頁面之間的切換,不會再涉及到底部導航控制器榕栏,它沒那功能畔勤;在 iOS 中,具體頁面間導航功能的就是我們今天要學習的 UINavigationController扒磁,即導航控制器庆揪,它能夠實現(xiàn)不同頁面之間的進入與退出。
二妨托、導航控制器(UINavigationController)
導航控制器就是負責頁面切換的缸榛,同樣,我們先來看看官方源碼注釋:
UINavigationController manages a stack of view controllers and a navigation bar.
導航控制器通過數(shù)據(jù)結構『椑忌耍』的方式來管理一組 ViewController内颗,并且它自帶導航欄。It performs horizontal view transitions for pushed and popped views while keeping the navigation bar in sync.
默認情況下敦腔,push 操作(進入下一級頁面)和 pop 操作(返回到上一級頁面)的轉場動畫是一個平移均澳,并且,導航控制器會保持導航欄(狀態(tài)的)同步会烙。
如果你覺得上面的注釋看不懂也沒關系负懦,接下來我會分析筒捺,知道的朋友可以直接跳下一小節(jié)(不太建議柏腻,有些細節(jié)『老司機』也不一定掌握 -_-|||)。我們先來看看 UIKit.UINavigationController 源碼系吭,了解其內部的核心對象和方法五嫂。
2.1、UINavigationController 分析
精華注釋.....
@available(iOS 2.0, *)
open class UINavigationController : UIViewController {
// 構造器,需要一個 UIViewController 作為根視圖控制器
// 初始化時沃缘,它會將這個 UIViewController 以 push 且沒有動畫的方式放入棧中
// 該 UIViewController 將是棧中第一個視圖控制器
public init(rootViewController: UIViewController)
// 默認使用平移動畫進入下一個頁面躯枢,如果當前的頁面已在棧頂,則沒有任何效果
open func pushViewController(_ viewController: UIViewController, animated: Bool)
// 返回頁面有三種方式:
// 1. 返回上一級頁面:即從哪里來的槐臀,回到哪里去
open func popViewController(animated: Bool) -> UIViewController?
// 2. 返回到指定的頁面:你可以使用該方法返回上一級面頁锄蹂,也可以直接返回到根頁面
// 對于返回到指定的頁面,則該頁面所在棧中之上的所有頁面都將出棧并銷毀(這些頁面的出棧不可見)
open func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]?
// 3. 返回到第一個頁面
open func popToRootViewController(animated: Bool) -> [UIViewController]?
// 當前棧中存在的頁面水慨,我們可以通過遍歷:
// 1. pop操作來返回到我們想要返回的頁面
// 2. 通過個數(shù)來做一些判斷操作(后面會用到)
open var viewControllers: [UIViewController]
@available(iOS 7.0, *)
// 這個沒有英文注釋得糜,不過,我們都會用到
// 我們也看到了它的出現(xiàn)是在 7.0 以后晰洒,而它的功能就是:(右)側滑返回上一級而面的手勢
// 因為蘋果手機屏幕越來越大了嘛朝抖,總是讓用戶點『返回』按鈕來返回到上一級,體驗不好嘛
open var interactivePopGestureRecognizer: UIGestureRecognizer? { get }
// 導航欄谍珊,管理 navigationItem治宣,同樣使用棧來管理(為啥?)
// 因為每個 VC 都對應著一個 navigationItem
open var navigationBar: UINavigationBar { get }
@available(iOS 3.0, *)
// tool條砌滞,含有 title 和 image
open var toolbar: UIToolbar! { get }
// 其它請自行查看
......
}
以上列出的方法和成員都是我們將來會用的著的侮邀,且會經(jīng)常打交道,不過贝润,類似于 UITabBarController豌拙,基本上只會打交道一次,為啥题暖?因為一般我們都會在封裝在基類中按傅,由基類來統(tǒng)一管理,如果每個頁面自己管理胧卤,則很容易亂套(蘋果自己對這塊都是非澄ㄉ埽混亂),而我給大家分享的枝誊,都是通過實踐來告訴大家避免踩坑的最終結果况芒。
2.2、擴展 UINavigationController 分析
extension UIViewController {
// 大家可以自行創(chuàng)建導航欄的外觀(左叶撒、右按鈕绝骚,標題,文字祠够、圖片压汪、顏色和透明度)
open var navigationItem: UINavigationItem { get }
// 這個屬性默認是 Swift: false / OC: NO,但這個屬性在遇到 UITabBarController 時會有用處
open var hidesBottomBarWhenPushed: Bool
// 我們通過 navigationController 來 push / pop 操作
open var navigationController: UINavigationController? { get }
}
2.3古瓤、導航欄元素:UINavigationItem 分析
我們可以結合著如下圖片來分析了解:
@available(iOS 2.0, *)
open class UINavigationItem : NSObject, NSCoding {
// 標題止剖,默認 nil腺阳,在 UI層級的最頂層
// 可以通過:Debug View Hierachy 查看視圖的層級
open var title: String?
// 可以自定義標題視圖(比如:放張圖片),只有它在 UI最頂層才有效
open var titleView: UIView?
// 返回鍵 (左邊按鈕)
open var backBarButtonItem: UIBarButtonItem?
// leftBarButtonItems 和 rightBarButtonItems 在以前只是一個單一按鈕穿香,現(xiàn)在管理著一組按鈕亭引;
// 這兩分別對應著每個數(shù)組中的第一個元素
@available(iOS 5.0, *)
// 左按鈕
open var leftBarButtonItems: [UIBarButtonItem]?
@available(iOS 5.0, *)
// 右按鈕
open var rightBarButtonItems: [UIBarButtonItem]?
}
主要就是以上元素,如果在這里你沒有任何疑惑皮获,那么我是非常疑惑的焙蚓!為啥?你沒發(fā)現(xiàn)有兩個元素都能控制『左側按鈕』么洒宝?分別是:
- backBarButtonItem主届;
- leftBarButtonItem;
如果同時設置了這兩會有什么結果待德?
前提:previousVC 是上一個頁面,nextVC 是下一個頁面将宪,當發(fā)生 push 時绘闷,有如下規(guī)則:
- 如果 nextVC 的 leftBarButtonItem != nil,那么將在 navigationBar 的左邊顯示 nextVC 指定的 leftBarButtonItem较坛;
- 如果 nextVC 的 leftBarButtonItem == nil印蔗,previousVC 的 backBarButtonItem != nil,那么將在 navigationBar 的左邊顯示 previousVC 指定的 backBarButtonItem丑勤;
- 如果兩者都為 nil 則:
- 3.1. nextVC 的 navigationItem.hidesBackButton = YES华嘹,那么 navigationBar 將隱藏左側按鈕;
- 3.2. 否則 navigationBar的左邊將顯示系統(tǒng)提供的默認返回按鈕法竞;
我們從以上規(guī)則中發(fā)現(xiàn):
- leftBarButtonItem 的優(yōu)先級比 backBarButtonItem 要高耙厚;
- backBarButtonItem 是來自上一個頁面,如果當前 VC 是第一個頁面岔霸,那么它沒有上一個頁面薛躬,也就沒有 backBarButtonItem;
- leftBarButtonItem 是來自當前頁面呆细,與上個頁面無關型宝,因此,如果當前 VC 是第一個頁面絮爷,那么設置了 leftBarButtonItem 就會很奇怪趴酣;
因此,請注意上面的左側按鈕規(guī)則坑夯,千萬不要同時設置岖寞,雖然有優(yōu)先級保證不會顯示兩個按鈕,但是你的顯示邏輯可能就不一樣了渊涝!
分析完了這么多慎璧,我們用一張大圖來總結一下:
三床嫌、UINavigationController 的使用
3.1跨释、使用方式
它的使用很簡單胸私,逃脫不開的模式,即 window.rootViewController 的設置有以下三種:
- 直接設置鳖谈;
// AppDelegate 中
window?.rootViewController = UINavigationController(rootViewController: XXXViewController())
- UITabBarController 嵌套 UINavigationController岁疼;
// AppDelegate 中
window?.rootViewController = MainTabBarController()
// MainTabBarController 中
let xxx = UINavigationController(rootViewController: XXXViewController())
xxx.tabBarItem.title = "xxx"
viewControllers = [xxx, ......]
- UINavigationController 嵌套 UITabBarController(可能再嵌套 UINavigationController,就和第2種差不多了)缆娃;
// AppDelegate 中
window?.rootViewController = UINavigationController(rootViewController: MainTabBarController())
// 可能再嵌套如下
// MainTabBarController 中
let xxx = UINavigationController(rootViewController: XXXViewController())
xxx.tabBarItem.title = "xxx"
viewControllers = [xxx, ......]
通過以上方式捷绒,我們能發(fā)現(xiàn),常用的模式不是第 1 種贯要,就是第 2 種暖侨。
3.2、實戰(zhàn)出真理
基于我們上一篇的例子崇渗,我們采用第 2 種方式來使用字逗。
- 簡單改造如下
- 修改 HomeViewController
- 運行模擬器
我們的導航欄(帶標題)就出來了,這里需要注意一點:iOS 7.0 后宅广,改為了扁平風格葫掉,這里的導航欄也就變成的半透明效果!
3.3跟狱、頁面跳轉 push & pop
- 修改我們的 HomeViewController
- 點擊 label俭厚,push 到下一級頁面
- 跳轉穩(wěn)定后,如下圖
四驶臊、總結
本篇只是分析了導航控制器和最基本的使用挪挤,下一篇,我們會根據(jù)實際的場景及需求(上小節(jié)最后一張圖关翎,我們可以看到电禀,push 到下級頁面,底部 tabbar 仍舊顯示笤休,雖然少數(shù) app 會這么做尖飞,但大多數(shù) app 都會隱藏),結合著 UITabBarController 和 UINavigationController 來講述我在實際工作中是如何來配置的店雅。