上篇,我们仿了京东的底部导航栏,显示了5个页面(UIViewController),它的作用你可以理解为页面之间的切换,每个页面都属于一级页面;而一级页面一般只是一些吸引用户的的主要流量入口,实际的功能页面都是通过这些一级页面的入口导航到下一级或是更深一级的页面(从用户角度来看,核心重要的页面最多不超过三级,否则层级太深,用户可能就没兴趣继续点下去了,除了『确认订单』和『支付』页面)。
N级页面之间的切换,不会再涉及到底部导航控制器,它没那功能;在 iOS 中,具体页面间导航功能的就是我们今天要学习的 UINavigationController,即导航控制器,它能够实现不同页面之间的进入与退出。
二、导航控制器(UINavigationController)
导航控制器就是负责页面切换的,同样,我们先来看看官方源码注释:
UINavigationController manages a stack of view controllers and a navigation bar.
导航控制器通过数据结构『栈』的方式来管理一组 ViewController,并且它自带导航栏。
It performs horizontal view transitions for pushed and popped views while keeping the navigation bar in sync.
默认情况下,push 操作(进入下一级页面)和 pop 操作(返回到上一级页面)的转场动画是一个平移,并且,导航控制器会保持导航栏(状态的)同步。
如果你觉得上面的注释看不懂也没关系,接下来我会分析,知道的朋友可以直接跳下一小节(不太建议,有些细节『老司机』也不一定掌握 -_-|||)。我们先来看看 UIKit.UINavigationController 源码,了解其内部的核心对象和方法。
2.1、UINavigationController 分析
精华注释.....
@available(iOS 2.0, *)
open class UINavigationController : UIViewController {
public init(rootViewController: UIViewController)
open func pushViewController(_ viewController: UIViewController, animated: Bool)
open func popViewController(animated: Bool) -> UIViewController?
open func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]?
open func popToRootViewController(animated: Bool) -> [UIViewController]?
open var viewControllers: [UIViewController]
@available(iOS 7.0, *)
open var interactivePopGestureRecognizer: UIGestureRecognizer? { get }
open var navigationBar: UINavigationBar { get }
@available(iOS 3.0, *)
open var toolbar: UIToolbar! { get }
......
以上列出的方法和成员都是我们将来会用的着的,且会经常打交道,不过,类似于 UITabBarController,基本上只会打交道一次,为啥?因为一般我们都会在封装在基类中,由基类来统一管理,如果每个页面自己管理,则很容易乱套(苹果自己对这块都是非常混乱),而我给大家分享的,都是通过实践来告诉大家避免踩坑的最终结果。
2.2、扩展 UINavigationController 分析
extension UIViewController {
open var navigationItem: UINavigationItem { get }
open var hidesBottomBarWhenPushed: Bool
open var navigationController: UINavigationController? { get }
2.3、导航栏元素:UINavigationItem 分析
我们可以结合着如下图片来分析了解:
@available(iOS 2.0, *)
open class UINavigationItem : NSObject, NSCoding {
open var title: String?
open var titleView: UIView?
open var backBarButtonItem: UIBarButtonItem?
@available(iOS 5.0, *)
open var leftBarButtonItems: [UIBarButtonItem]?
@available(iOS 5.0, *)
open var rightBarButtonItems: [UIBarButtonItem]?
主要就是以上元素,如果在这里你没有任何疑惑,那么我是非常疑惑的!为啥?你没发现有两个元素都能控制『左侧按钮』么?分别是:
backBarButtonItem;
leftBarButtonItem;
**如果同时设置了这两会有什么结果?**
官方没有说,我们都是通过实践得出的真理:
前提:previousVC 是上一个页面,nextVC 是下一个页面,当发生 push 时,有如下规则:
如果 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的左边将显示系统提供的默认返回按钮;
leftBarButtonItem 的优先级比 backBarButtonItem 要高;
backBarButtonItem 是来自上一个页面,如果当前 VC 是第一个页面,那么它没有上一个页面,也就没有 backBarButtonItem;
leftBarButtonItem 是来自当前页面,与上个页面无关,因此,如果当前 VC 是第一个页面,那么设置了 leftBarButtonItem 就会很奇怪;
因此,请注意上面的左侧按钮规则,千万不要同时设置,虽然有优先级保证不会显示两个按钮,但是你的显示逻辑可能就不一样了!
分析完了这么多,我们用一张大图来总结一下:
三、UINavigationController 的使用
3.1、使用方式
它的使用很简单,逃脱不开的模式,即 window.rootViewController 的设置有以下三种:
直接设置;
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, ......]
通过以上方式,我们能发现,常用的模式不是第 1 种,就是第 2 种。
3.2、实战出真理
基于我们上一篇的例子,我们采用第 2 种方式来使用。
简单改造如下
修改 HomeViewController
运行模拟器
我们的导航栏(带标题)就出来了,这里需要注意一点:iOS 7.0 后,改为了扁平风格,这里的导航栏也就变成的半透明效果!
3.3、页面跳转 push & pop
修改我们的 HomeViewController
点击 label,push 到下一级页面
跳转稳定后,如下图
本篇只是分析了导航控制器和最基本的使用,下一篇,我们会根据实际的场景及需求(上小节最后一张图,我们可以看到,push 到下级页面,底部 tabbar 仍旧显示,虽然少数 app 会这么做,但大多数 app 都会隐藏),结合着 UITabBarController 和 UINavigationController 来讲述我在实际工作中是如何来配置的。