构建你今天想要用的东西就好,而不是你觉得人们在某种程度上会使用的东西。
— Paul Graham
首先,什么是导航控制器?比如 table views。导航控制器在 iOS应用是另一个自定义 UI部分。它为分层内容提供了一个向下的界面。看看内置照片应用,YouTube和记事本。他们都使用导航控制器来显示分层内容。一般,你用一个视图控制器的堆栈与导航控制器联合起来为你的应用构建一个尽量简洁的界面。尽管如此,这不意味着你必须一起使用它们。导航控制器可以和任何风格的视图控制器一起工作。
Storyboards里的场景(Scenes)和延续(Segues)
直到现在,我们仅仅在 FoodPin应用的 Storyboard里布局了一个 table view 控制器。Storyboard允许你做得更多。你可以在 storyboard 里添加更多的视图控制器来连接他们。所有这些工作能够在没有一行代码的情况下完成。当用 storyboards 工作时,场景(scenes)和延续(segues)是你必须知道的两项。在 storyboard 里,一个场景通常涉及到屏幕上内容(如一个视图控制器)。segues位于2个场景之间,表示一个场景到另一个的切换。Push 和 Modal 是两种常见类型的转变。
Quick note:在 Xcode7里,介绍一个新的特性叫做 storyboard references,这个参考让 storyboards 更易于管理和更模块化。当你的工程变得大而复杂时,你可以把一个大的 storyboard分割成多个 storyboard 然后用 storyboard references连接他们。当你和你的团队成员合作创建一个 storyboard 时这个特性是特别有用的。
创建导航控制器
我们将通过内嵌table view 控制器进入一个导航控制器来继续在 Food Pin应用上工作。当一个用户选择任何餐厅时,这个应用导航到下个屏幕来显示餐厅细节。
如果你已经关闭了 FoodPin 工程,现在是时候打开 Xcode 然后再次打开工程。选择 Main.storyboard 来切换到界面构建器编辑。Xcode 提供一个内嵌特性来使它在一个导航控制器里很容易嵌入任何视图控制器。选择 table view 控制器然后点击菜单里的”Editor”。选择“Embed in”>“Navigation Controller”。
Xcode 自动在一个导航控制器嵌入控制器。我们设置导航栏的标题。选择 table view 控制器的导航栏。在 Attributes inspector 下,把title 值改成 Food Pin。
现在运行应用看看它看起来如何。这个应用和以前一样,但是添加了一个导航栏。
添加一个视图控制器细节
很简单,对吗?仅仅点几下,你已经给你的应用添加一个导航栏。不过这里漏掉了显示餐厅细节的视图控制器。当用户选择一个餐厅,应用传递给细节视图控制器然后显示餐厅细节。
在视图构建器里,从对象控制器里拖出一个视图控制器来创建细节视图控制器。我们让细节视图越简单越好。我们仅仅在细节视图里显示餐厅照片。从对象库里拖曳一个图像视图到视图控制器。调整它的大小来适应适合,然后添加为图像视图添加间距约束。为了确保图片正确的缩放,进入 Attributes inspector 把 mode从 Scale to Fill 改成 Aspect Fill。
现在你在 storyboard 里配置了两个视图控制器。这里有个问题,你如何才能把他们连接在一起?在 storyboard 里,我们需要通过一个 segue 来连接他们。在音乐方面,一个 segue 是两段音乐之间无缝的过渡。在 storyboard 里, 两个场景之间的过渡叫做 segue。
当用户按下单元格时,table view 控制器将传递到细节场景。所以我们将添加一个 segue 来连接 prototype cell 和细节场景。添加一个 segue 对象是很容易的。按住 control 键,点击 prototype cell 然后拖到细节视图控制器。
如果你觉得在 storyboard editor 里选择 prototype cell 很难,打开文档大纲视图,把 prototype cell 拖到 view controller。当你松开按钮时,一个弹出菜单会出现,给你来选择 segue 的类型。选择类型为“Show”,然后你会看到在 view controller 之间有一个接头。
一旦选好了,Xcode 用一个 show segue自动连接 prototype cell 和细节视图控制器。
在 iOS9里,它支持以下的 segue 类型:
- Show - 当使用 show 类型时,内容被推送到当前视图控制器堆栈的顶上。一个返回按钮将显示在导航栏来返回原始视图控制器。我们为导航控制器使用这个 segue 类型。
- Show detail - 类似于 show 类型,但是细节(或目标)视图控制器里的内容取代当前视图控制器堆栈的顶部。例如,如果你在 FoodPin 应用 里选择show detail 而不是 show,那样在细节视图里将没有导航栏和返回按钮。
- Present modally - 程序化的显示内容。使用时,细节视图控制器将从底部开始动画效果然后覆盖整个 iphone屏幕。一个“present modally”segue 的好例子是内置日历应用 的“add”特性。当你点击应用里的+按钮时,它会从底部引出一个“新事件”。
- Present as popover - 作为一个弹窗固定到现有的视图来呈现内容。Popover 通常能在 iPad 应用中找到。下图为你显示了一个例子。从 iOS8开始,你也可以在 iPhone 应用上使用 popover segue。
Quick note:这些 segue 类型从 iOS8开始被弃用:Push,Modal,Popover,Replace。
现在,你可以准备运行应用。登陆时,选择一个餐厅,应用应该会导航到细节视图控制器。同时,细节视图控制器仅仅显示一个黑色的屏幕。好的消息是你已经创建了一个导航界面。
没有写一行代码,你已经在你的应用里添加了一个导航控制器。但是,我猜在你的脑海里会有一些疑问:
- 你如何才能从 RestaurantTableViewController 传递餐厅信息到细节视图控制器?
- 你如何才能在细节视图控制器里显示选择的餐厅图片。
我们待会将会一个个的解决它们。
在进入下一章之前,我们来做一点小的调整。当你运行应用程序选择一个单元格时,它会导航到空白的屏幕,显示一个我们曾经在第十章实现的动作表单。我们不再需要那个动作表单。之后我们将在细节视图控制器里添加相同的功能。因此,从 RestaurantTableViewController.swift 里移除 tableView(_:didSelectRowAtIndexPath:) 方法。
Quick tip:有时你可能仅仅想要注释掉一个代码块而不是删除它们。Xcode 提供一个快捷键来注释多行代码。首先,选择代码块然后按住 command+slash。Xcode 会自动用注释指示器标记(comment indicators)代码块。想要移除注释,再做一次同样的程序。
为细节视图控制器创建一个新的类
Okay,我们回到细节视图控制器。我们的目标是在视图控制器里为被选择的餐厅更新图片。视图控制器现在默认与 UIViewController 类相连。事实是 UIViewController 类仅仅提供基本的视图管理模式。这里面没有变量用于储存餐厅图片。明显的,我们必须扩展 UIViewController 来创建我们自己的类以便于我们能为图片视图添加新的变量。
在工程导航器里,右击”FoodPin”文件夹然后选择”New File…”。选择”Cocoa Touch Class”作为类模板。把这个类命名为 RestaurantTableViewController 然后把它设置为 UIViewController 的一个子类。点击”Next”然后在你的工程文件夹里保存文件。
和平常一样,分配 DetailViewController 类到 storyboard 里的细节视图控制器。在界面构建器里,选择细节视图控制器然后打开 Identity inspector。改变自定义类到 RestaurantTableViewController。
给自定义类添加变量
我们必须在自定义类里添加一堆东西:
- 创建一个变量叫做 restaurantImage — 当用户在 table view 控制器里选择一个餐厅时,应该有个办法来传递图片名给细节视图。这个变量将被用于数据传递。
- 为图片视图创建一个叫做 restaurantImageView 的outlet — 我们需要一个参考来更新细节图像控制器的图像视图,所以我们不得不创建一个 outlet。
Okay,给 RestaurantTableViewController 类添加下面的代码。
@IBOutlet var restaurantImageView:UIImageView!
var restaurantImage = “”
接下来,在 restaurantImageView 变量和细节视图控制器的图片视图之间建立联系。回到 Main.storyboard。右击文档大纲里的Restaurant Detail View Controller对象。在弹出菜单里,连接 restaurantImageView outlet 和图像视图控制器。
既然你已经联系了变量和故事板里的图像视图对象,但是仍然有一件事没做。你还没有读取图片。图片视图应该显示被选择的餐厅图片。在RestaurantTableViewController 类的 viewDidLoad 方法里,添加一行代码。你的方法应该看起来像这样:
overrid func viewDidLoad() {
super.viewDidLoad()
//加载视图后做任何额外的设置。
restaurantImageView.image = UIImage(named:restaurantImage)
}
当视图加载到内存里时,viewDidLoad 方法被调用。在这个方法里,你能提供额外的自定义视图。在上面,我们简单的设置图像视图的图像给被选择餐厅的图像。
试着编译运行你的应用。在选择一个餐厅之后这个细节视图仍然是空白的。我们仍然搞错了一件事。我们没有从 table view控制器传递餐厅图像到细节视图控制器。这是为什么 restaurantImage 变量没有被分配任何值。
用 segues 传递数据
接下来是本章的核心部分,关于用 segues 传递数据。一个 segue 管理视图控制器之间的传递,包含了涉及到过渡的视图控制器。当一个 segue 触发时,在可视化过渡出现之前,storyboard 运行时通过调用 prepareForSegue 方法通知源视图控制器(如R estaurantTableViewController).默认prepareForSegue 方法的实施没做任何事。通过重写方法,你可以传递任何相关数据到新的控制器,这个控制器在我们工程叫做 RestaurantDetailViewController。
segues 可以被很多来源触发。随着你的 storyboard 变得更复杂,在视图控制器之间你有超过一个的 segue是很有可能的。因此,最好的实践是给每个在 storyboard 里的 segue 一个唯一的标识符。这个标识符是一个字符串,用来区分 segue。为一个 segue 分配一个标识符,在 storyboard 编辑器里选择这个 segue,然后进入 Attributes inspector。设置 identifier 值为 showRestaurantDetail。
随着配置好 segue,插入以下在 RestaurantTableViewController.swift 里的代码来重写 prepareForSegue 方法的默认实施:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == “showRestaurantDetail” {
if let indexPath = tableView.indexPathForSelectedRow {
let destinationController = segue.destinationViewController as!
RestaurantDetailViewController
destinationController.restaurantImage = restaurantImages[indexPath.row]
}
}
}
第一行代码被用来核查 segue 的标识符。这个代码块仅仅对 showRestaurantDetail segue 生效。在代码块里,我们首先通过访问 tableView.indexPathForSelectedRow 来检索选择的行。indexPath 对象应该包含选择的单元格。
一个 segue 对象同时包含源和目标视图控制器来参与过渡。你使用segue.destinationViewController来检索目标控制器。在这种情况下,目标控制器是 RestaurantDetailViewController对象。这就是为什么我们必须用 as! 操作符来向下转换它。最终,我们传递选择餐厅的图片到目标控制器。