• 实现导航


    在本章中,我们将使用导航控制器并继续创建FoodTracker app的导航流程。在课程结束后,你将有一个导航策略和交互流程。当你完成时,你的app看起来如下所示:

    学习目标
    在课程结束时,你将学会:
    1.在storyboard中的导航控制器内嵌入一个已经存在的视图控制器

    2.在两个视图控制器之间创建桥梁

    3.在storyboard的Attributes inspector内编辑一个segue的属性

    4.通过使用prepareForSegue(_:sender:)来在视图控制器之间传递数据

    5.执行一个unwind segue(用于实现向后导航的一个segue类型)

    6.使用stack view来创建健壮,灵活的布局(Xcode 7.0)

    添加一个segue到向前导航

    数据显示如预期一样,是时候提供一个方法来从meal list场景到meal场景的导航了。场景之间的转换通过调用segues(类似android的intent)

    在创建一个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)。但这两个按钮没有绑定动作,你点击它们没有任何反应。接下来我们会配置这两个按钮的动作

    使用自动布局完成UI(Xcode7下可用)

    这是一段时间以来,对于你原来建立的用户界面,有很多事情发生了改变。在这一点上,你不用对你的布局做出任何改变,所以自动布局看起来很好用。
    要做到这一点,你需要对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,并且不会添加任何新的菜谱

  • 相关阅读:
    wordpress通过$wpdb获取一个分类下所有的文章
    WordPress的摘要显示方式
    WordPress简洁的SEO标题、关键词和描述
    WordPress获取特色图像的链接地址
    WordPress的Bootstrap面包屑导航
    destoon 6.0 手机站支持在所有浏览器访问
    dede织梦5.7的安全防护设置
    WordPress主题制作:基础样式文件
    LInux常用到的命令(面试)
    1030 完美数列 (25分) PAT-B
  • 原文地址:https://www.cnblogs.com/tianjian/p/4626459.html
Copyright © 2020-2023  润新知