浅析UINavigationController

基本原理

UINavigationController是一个容器,它提供基本的视图之间跳转的功能。viewcontroller被它包装起来,每一个viewController有自己的navigation item,当用户在左右滑动的时候,其实就是UINavigationController在起作用,它把另一个viewController放进屏幕替代当前屏幕上的viewController,同时这个viewController有自己的Navigation Item,这个item有回退键和title。UINavigationController提供一个UINavigation bar,这个bar用来显示当前屏幕上的viewController的Navigation Item。

如何把当前viewController放进UINavigationController

进入storyboard,选中当前viewController,然后在状态栏上选择editor -> embed in -> Navigation Controller。之后,所有指向这个viewController的指针都指向了包装它的Navigation Controller。

如何在不同的viewController之间跳转

在上一步完成之后,这个viewController就有了一个Navigation Controller提供的Navigation bar,我们可以在这个Navigation Bar上添加一些Bar Button,然后ctrl+drag这个按钮到其他viewController上,松开手时系统会提示你一些segue的选择。iOS 9提供了4种跳转方式:show, show detail, present modally, present as popover。如果选择show,当点击按钮跳转到另一个viewController时,它会有Navigation bar,并且还有一个back按钮,点击后回到之前的页面。如果选在present modally,点击按钮跳转后,页面上没有navigation bar,自然也没有回退按钮,通常我们为了让这个viewController有Navigation Bar,我们需要把它再次包装到另一个Navigation Controller里,最后的效果如下:

如何在不同viewController之间传递数据

例如A,B之间跳转,我们可以在B中定义delegate和方法,然后让A实现delegate和方法,同时B中保存一个变量,它指向A。viewController有一个方法叫做prepareForSegue,它在两个controller跳转之前被调用,我们可以在里面放上准备数据,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "AddItem" {
        let navContr = segue.destinationViewController as? UINavigationController
        let contr = navContr?.topViewController as? AddItemViewController
        contr?.delegate = self
    } else if segue.identifier == "EditItem" {
        let navContr = segue.destinationViewController as? UINavigationController
        let contr = navContr?.topViewController as? AddItemViewController
        contr?.delegate = self

        if let indexPath = tableView.indexPathForCell(sender as! UITableViewCell) {
            contr?.itemToEdit = items[indexPath.row]
        }
    }
}

因为一个controller可以有很多种方法跳到另一个controller(tap button, tap row…),所以可以有很多segue在两个controller之间,我们需要给每一个segue一个别名来区别它们,上面的代码的意思是,如果segue是AddItem,然后就找到这个segue的目标controller,因为下一个controller被Navigation Controller包装,所以不是直接跳转而是通过包装Navigation Controller中转,得到Navigation Controller后找到它的topViewController,即最终目标,然后设置它的delegate变量为当前controller,这样就能在B中通过delegate变量调用A中实现的delegate方法,达到操作A中数据的目的。

B中定义的delegate和变量:

1
2
3
4
5
6
7
8
9
10
11
protocol AddItemViewControllerDelegate: class {
    func addItemViewControllerDidCancel(controller: AddItemViewController)
    func addItemViewController(controller: AddItemViewController, didFinishAddingItem item: ChecklistItem)
    func addItemViewController(controller: AddItemViewController, didFinishEditingItem item: ChecklistItem)
}

class AddItemViewController {
  ...
  var itemToEdit: ChecklistItem?
    ...
}

A中实现delegate和其方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ChecklistViewController: AddItemViewControllerDelegate {
  ...
  func addItemViewController(controller: AddItemViewController, didFinishAddingItem item: ChecklistItem) {
    let indexRow = items.count
    items.append(item)
    let indexPath = NSIndexPath(forRow: indexRow, inSection: 0)
    tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
    dismissViewControllerAnimated(true, completion: nil)
}
   func addItemViewController(controller: AddItemViewController, didFinishEditingItem item: ChecklistItem) {
    if let indexRow = items.indexOf(item) {
        let indexPath = NSIndexPath(forRow: indexRow, inSection: 0)
        if let cell = tableView.cellForRowAtIndexPath(indexPath) {
            configureTextForCell(cell, item: item)
        }
    }
    dismissViewControllerAnimated(true, completion: nil)
  }
  ...
}