在本章中,咱们将使用导航控制器并继续建立FoodTracker app的导航流程。在课程结束后,你将有一个导航策略和交互流程。当你完成时,你的app看起来以下所示:android
学习目标
在课程结束时,你将学会:
1.在storyboard中的导航控制器内嵌入一个已经存在的视图控制器swift
2.在两个视图控制器之间建立桥梁app
3.在storyboard的Attributes inspector内编辑一个segue的属性ide
4.经过使用prepareForSegue(_:sender:)来在视图控制器之间传递数据布局
5.执行一个unwind segue(用于实现向后导航的一个segue类型)学习
6.使用stack view来建立健壮,灵活的布局(Xcode 7.0)动画
添加一个segue到向前导航ui
数据显示如预期同样,是时候提供一个方法来从meal list场景到meal场景的导航了。场景之间的转换经过调用segues(相似android的intent)spa
在建立一个segue之间,你须要配置你的场景。首先你把table view controller放入一个导航控制器的内部。导航控制器经过向前和向后来管理一系列view controller的转换。经过一个特定的导航控制器来管理一个view controllers集,这被称为导航堆栈,第一个添加到栈中的会成为root view controller,它永远不会从导航堆栈弹出。设计
添加导航控制器到你的meal list场景
1.打开你的storyboard,Main.storyboard
2.选择table view controller(你也能够经过 scene dock来选择)
3.在table view controller被选中的状况下,选择Editor > Embed In > Navigation Controller
Xcode会添加一个新的导航控制器到你的storyboard中,设置storyboard的入口点,并在新的导航控制器和已存在的table view控制器之间建立一个关系
在画布中,会有一个链接到控制器icon,它是root view controller的关系。table view controller是导航控制器的root view controller。storyboard的入口点设置为导航控制器,是由于导航控制器是一个现有的table view controller的容器。你可能注意到table view顶部有一个栏了。这就是导航栏。每个在导航栈中得到一个导航栏的控制器,能包含向前,向后导航。接下来,你须要添加一个按钮到这个导航栏来过渡到meal场景。
检查站:运行你的app。在你table view的上方,应该能够看到额外的空间。这是导航控制器提供的导航栏。导航栏会扩展它的背景到状态栏的顶部,因此状态栏不会和你的内容重叠了
为场景配置导航栏
如今,你将添加一个标题和一个按钮到导航栏。导航栏从当前显示的导航controller中,得到他们的标题。导航控制器自己没有标题,它包裹的内容才有标题。你使用meal list的导航item设置标题,而不是在导航栏直接设置它。
在meal list配置导航栏
1.双击meal list场景中的导航栏(点击中间)
会出现一个光标,让你输入文本
2.输入Your Meals而后按下Return来保存
3.打开Object library
4.找到 Bar Button Item对象
5.拖动Bar Button Item对象到导航栏的最右边
一个Item的按钮会出如今,你松开的地方
6.选择 bar button item,打开 Attributes inspector
7.在 Attributes inspector,在标签Identifer旁,选择Add
按钮会变成一个(+)
检查点,执行你的APP,导航栏会显示一个标题和一个(+)按钮。如今这个按钮不会作任何事,接下来咱们会修复它
你想要经过点击(+)按钮跳转到meal场景,因此咱们会经过点击按钮触发一个segue来跳转到那个场景
配置(+)按钮
1.在画布上,选择(+)按钮
2.按住Control键拖动按钮到meal场景中
一个Action Segue的快捷菜单出现,松开的地方
Action Segue菜单容许你选择segue的类型
4.这里咱们选择Show
Xcode设置Action Segue并配置meal场景用于显示,如今Interface Builder中的界面以下:
检查站:执行你的APP,你如今能够点击(+)按钮并能够从meal list场景导航到meal场景了。由于你使用导航控制器来显示一个segue,那向后导航已经自动帮你处理好了,会自动出现一个back按钮在你的meal场景中。这意味着你能点击back按钮回到meal list场景
推送风格导航用于显示segue。可是在增长item时,这可能并非你想要的。推送导航设计于钻取界面,不管用户选择什么,你应该提供更多信息。增长一个item,另外一方面是一个模式的操做,用户执行一个动做,这是完整的,自成体系的,而后从场景返回到主导航。对于这个类型的场景展现,有一个合适的方法叫modal segue。(须要用户在展现的控制器中执行一个操做,才能返回到主流程)
若是要删除已存在的segue并建立一个新的,在Attributes inspector中简单的改变segue的风格便可。如大多数在storyboard可选的元素同样,你能使用Attributes inspector来编辑一个segue的属性
改变segue的风格
1.在meal list场景和meal场景之间选中segue(那个小箭头)
2.在Attributes inspector中,找到Seque标签,下拉选择Present Modally
3.在Attributes inspector中,找到Identifier标签,输入AddItem,而后Return
后面咱们会须要这个标示符来识别segue
一个modal的视图控制器不被添加到导航栈,所以它不会有一个导航栏。然而,你想要保持导航栏来提供给用户视觉连续性。当展现modal时,为了给meal场景一个导航栏,它会嵌入在本身的导航控制器中
添加一个导航控制器到meal场景
1.选中 meal scene
2.选中meal场景的状况下,选择Editor > Embed In > Navigation Controller
和之前同样,Xcode添加一个导航控制器并显示一个导航栏在meal场景的顶部,接下来,配置这个导航栏,咱们添加两个按钮Cancel,Save。和一个标题。你会用来这两个按钮来执行一些动做。
在meal场景中配置导航栏
1.双击meal场景中的导航栏(点中间),出现一个光标,让你输入文本
2.输入New Meal而后按Return
3.在Object library中拖动Bar Button Item对象到导航栏最左边
4.在Attributes inspector中,找到Identifier标签,选择Cancel。
按钮的文本变成了Cancel
5.在Object library中拖动Bar Button Item对象到导航栏右边
6.在Attributes inspector中,找到Identifier标签,选择Save
按钮的文本变成了Save
检查站:执行的app,点击(+)按钮。而后会出现meal场景,但meal场景中不会有back导航。你会在上方看见两个按钮(Cancel和Save)。但这两个按钮没有绑定动做,你点击它们没有任何反应。接下来咱们会配置这两个按钮的动做
这是一段时间以来,对于你原来创建的用户界面,有不少事情发生了改变。在这一点上,你不用对你的布局作出任何改变,因此自动布局看起来很好用。
要作到这一点,你须要对stack view作一些简单的调整。
更新stack view的布局
1.在meal场景中,选中stack view
2.在画布的底部右边,打开Resolve Auto Layout Issues菜单
3.选择Update Constraints
元素的位置仍是没变,但 stack view如今被固定于导航栏上,而不是View的顶部边缘。如今UI看起来以下:
检查站:执行的app。一切看起来都和之前同样
在Meal List中保存新的Meals
接下来咱们要实现一个添加新菜谱的功能。当用户输入菜谱名称,评级和照片时,点击Save按钮,你想要MealViewController配置一个Meal对象,而后返回适当的信息到
MealTableViewController的菜谱列表场景中来显示。首先咱们添加一个Meal属性到MealViewController中
添加一个Meal属性到MealViewController中
1.打开MealViewController.swift
2.找到MealViewController.swift,在ratingControl的outlet下,添加如下属性
/* This value is either passed by `MealListTableViewController` in `prepareForSegue(_:sender:)` or constructed as part of adding a new meal. */ var meal = Meal?()
这个属性是可选的,由于它有可能为nil的状况
你只须要在点击Save按钮时,关心配置和传递Meal。因此咱们须要添加一个Save按钮的outlet到MealViewController.swift中
链接Save按钮到MealViewController代码中
1.打开你的storyboard
2.打开assistant editor
3.在storyboard中,选中Save按钮
4.按住Control键拖动Save按钮到MealViewController.swift中的
ratingControl属性下
5.在弹出的对话框中,Name标签旁,输入saveButton,而后点击Connect
建立一个Unwind Segue
如今的任务是当用户点击Save按钮时,传递Meal对象到MealTableViewController。当用户点击Cancel按钮时,则取消。
要作到这点,你将使用一个unwind segue。一个unwind segue,能够经过一个或多个segues向后返回到一个已存在的view controller实例中。你使用unwind segues来实现反向导航。
每当一个segue被触发,它提供一个让你添加代码并执行的地方。这个方法叫prepareForSegue(_:sender:),它可让你存储数据并作一些必要的清理工做。你能够在MealViewController
中实现这个方法来作到这点
在MealViewController中实现prepareForSegue(_:sender:)方法
1.返回到standard editor
2.打开MealViewController.swift
3.在MealViewController.swift上方,添加注释
// MARK: Navigation
4.在注释下方,添加以下代码
// This method lets you configure a view controller before it's presented. override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { }
5.在prepareForSegue(_:sender:)方法中,添加if语句
if saveButton === sender { }
(===)操做符用来检查对象的引用是否相同,即
saveButton和sender是不是同一个对象。若是是,if语句会执行
6.在if语句中,添加以下代码
let name = nameTextField.text ?? "" let photo = photoImageView.image let rating = ratingControl.rating
这段代码从当前文本框,选中的image,和评级数据三个方面建立了常量
注意,在name
这行使用了空值合并运算符(??)。这个运算符对于可选变量有值时,返回一个值,若是可选变量为nil时,则返回默认值。这里咱们经过nameTextField.text来返回一个值,他可能为空,若是用户没有在文本框中输入内容,那么就为nil,则返回空串("")
7.接着在if语句中,添加以下代码
// Set the meal to be passed to MealListTableViewController after the unwind segue. meal = Meal(name: name, photo: photo, rating: rating)
这段代码用来在segue执行前使用适当的值来配置meal属性
如今完整的prepareForSegue(_:sender:)方法看起来以下:
// This method lets you configure a view controller before it's presented. override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if saveButton === sender { let name = nameTextField.text ?? "" let photo = photoImageView.image let rating = ratingControl.rating // Set the meal to be passed to MealListTableViewController after the unwind segue. meal = Meal(name: name, photo: photo, rating: rating) } }
接下来咱们建立的unwind segue会添加一个动做方法到目标视图控制器(就是segue将要去的视图控制器)。这个方法必须标记为IBAction属性来获取一个segue(UIStoryboardSegue
)做为参数。由于你想要unwind segue返回到meal list场景,你须要添加一个这种格式的动做方法到 MealTableViewController.swift中。
在这个方法中,你将写逻辑来来添加新的菜谱到meal list数据中并会在meal list场景下的table view内添加新的一行
添加一个动做方法到MealTableViewController
1.打开MealTableViewController.swift
2.在MealTableViewController.swift中,(})以前,添加以下代码:
@IBAction func unwindToMealList(sender: UIStoryboardSegue) {
}
3.在unwindToMealList(_:)动做方法内,添加如下if语句
if let sourceViewController = sender.sourceViewController as? MealViewController, meal = sourceViewController.meal{ }
if语句中会发生不少事情。
代码使用可选类型强制转换操做符(as?),试图子类强转到源view controller的segue到MealViewController类型。你须要子类强转,由于sender.sourceViewController是UIViewController类型,但你须要使用MealViewController工做。
这个操做符返回一个可选值,若是子类强转不可行,那么它将会是nil。若是子类强转成功,代码会分配view controller到局部常量sourceViewController,并检查是否
sourceViewController中的
meal属性为nil。若是meal属性非nil,代码分配属性值到局部常量meal并执行if语句。
4.在if语句中,添加如下代码
// Add a new meal. let newIndexPath = NSIndexPath(forRow: meals.count, inSection: 0)
代码会计算table view中新插入的cell的显示位置,并存储它在局部常量newIndexPath中
5.在if语句中,添加如下代码
meals.append(meal)
添加新的菜谱到已存在的meals列表中(数据模型)
6.在if语句中,添加如下代码:
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
会有个动画添加新的行(cell)到table view中,它会包含新的菜谱信息。.Bottom动画选项会显示从底部滑动插入
你稍后将完成一个更高级的方法实现,但如今unwindToMealList(_:)动做方法看起来以下:
@IBAction func unwindToMealList(sender: UIStoryboardSegue) { if let sourceViewController = sender.sourceViewController as? MealViewController, meal = sourceViewController.meal { // Add a new meal. let newIndexPath = NSIndexPath(forRow: meals.count, inSection: 0) meals.append(meal) tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom) } }
如今你须要建立一个实际的unwind segue来触发这个动做方法
链接Save按钮到unwindToMealList动做方法
1.打开你的storyboard
2.在画布中,按住Control键拖动Save按钮到meal场景的Exit items上
当你松开时,会出现一个提示
3.从快捷菜单中选择unwindToMealList:
如今当用户点击Save按钮时,导航返回到meal list场景,在此期间,unwindToMealList(_:)动做方法会被调用
检查站:执行你的app。如今当你点击(+)按钮时,建立一个新的菜谱,而后点击保存,你将会看见新的菜谱出如今你的meal list中
若是你没有看到在快捷菜单中unwindToMealList方法,确保该方法具备正确的签名:@IBAction func unwindToMealList(sender: UIStoryboardSegue)
当用户没有输入一个Item Name时,禁用保存
若是没有name时,用户点击save按钮会发生什么?由于在MealDetailTableViewController中的meal属性是可选的,因此若是没有name,那么你的初始化程序会失败,Meal对象不会建立,也不会添加到meal list场景中。但你能够在软键盘消失前,检测用户是否指定了一个有效的name,若是用户意外的没有添加meal的name,那么咱们禁用Save按钮
当没有name时,禁用Save按钮
1.在MealViewController.swift找到 // MARK: UITextFieldDelegate
2.而后添加另外一个UITextFieldDelegate协议内的方法
func textFieldDidBeginEditing(textField: UITextField) { // Disable the Save button while editing. saveButton.enabled = false }
当编辑开始时,或当软键盘显示时,textFieldDidBeginEditing会被调用。而后咱们经过代码来禁用Save按钮
3.在textFieldDidBeginEditing(_:)方法下方添加另外一个方法
func checkValidMealName() { // Disable the Save button if the text field is empty. let text = nameTextField.text ?? "" saveButton.enabled = !text.isEmpty }
这个帮助方法用来检查当文本框为空时,禁用Save按钮
4.找到textFieldDidEndEditing(_:)方法,添加以下代码:
checkValidMealName()
navigationItem.title = textField.text
第一行是检查文本框是否为空,来启用或禁用Save按钮。第二行是设置场景的标题为文本框中的文本
6.找到viewDidLoad()方法,而后添加以下代码:
// Enable the Save button only if the text field has a valid Meal name. checkValidMealName()
首先,载入界面后,确保Save按钮是被禁用的
完整的 viewDidLoad()方法以下
override func viewDidLoad() { super.viewDidLoad() // Handle the text field’s user input through delegate callbacks. nameTextField.delegate = self // Enable the Save button only if the text field has a valid Meal name. checkValidMealName() }
完整的textFieldDidEndEditing()方法以下
func textFieldDidEndEditing(textField: UITextField) { checkValidMealName() navigationItem.title = textField.text }
检查站:执行的APP。如今当你点击(+)按钮时,Save按钮首先会被禁用,直到你输入一个有效的meal name并关闭软键盘后Save按钮可用
取消新菜谱的添加
用户可能决定取消添加一个新的菜谱,并返回到meal list场景中。对于这点,咱们须要实现Cancel按钮的行为
建立和实现取消动做方法
1.打开你的storyboard
2.打开assistant editor
3.在storyboard中,选中Cancel按钮
4.按住Control键拖动Cancel按钮到 MealViewController.swift
代码中// MARK: Navigation注释的下方
5.在弹出的对话框中,Connection旁选择Action
6.Name标签旁,输入cancel
7.Type标签旁,选择UIBarButtonItem
8.点击Connect,出现如下代码:
@IBAction func cancel(sender: UIBarButtonItem) {
}
9.在cancel(_:)动做方法中,添加如下代码:
dismissViewControllerAnimated(true, completion: nil)
这行代码是让meal场景消息,没有存储任何信息
你完整的cancel(_:)动做方法以下:
@IBAction func cancel(sender: UIBarButtonItem) { dismissViewControllerAnimated(true, completion: nil) }
检查站:执行你的APP,如今当你点击(+)按钮后,点击Cancel按钮,你将导航回到meal list,而且不会添加任何新的菜谱