iOS Apps 开发(Swift)(9)——Implement Navigation

来源:互联网 发布:窄斑凤尾蛱蝶 标本淘宝 编辑:程序博客网 时间:2024/06/05 14:53

前言:网上一直没有找到用Swift开发IOS的好的教程,所以找了官网的文档翻译一下算了。如有错误欢迎指正。博主首发CSDN,mcf171专栏。

博客链接:mcf171的博客

原文链接:Implement Navigation

——————————————————————————————

在本次课程中,我们将使用导航控制器并且创建FoodTracker app的导航控制流,在课程的最后我们将有一个完整的当行模式和交互流。当我们最终完成app之后,界面如图所示


学习目标

在本次课程中,你可以了解到:

  • 在 storyboard 的导航控制器中嵌入一个已有的 view controller
  • 在 view controller 之间创建segues
  • 通过属性查看器编辑在storyboard 中 segue 的属性
  • 使用prepareForSegue方法在 view controllers 之前传输数据
  • 使用一个解绑的 segue
  • 使用stack view 来创建鲁邦、灵活性强的布局

给向前导航增加一个Segue

当数据如我们期望的那样进行显示的时候,我们就可以给我们的菜品场景添加一个导航。场景之前的切换也就称为Segues

给我们的菜品列表场景添加一个导航
1、打开Main.storyboard
2、通过点击 场景 dock 来选择table view

3、选中 table view controller之后点击Editor > Embed In > Navigation Controller.
Xcode会自动为storyboard增加一个新的导航控制器,进入点的位置也会指向这个导航控制器。同时我们需要在导航控制器和已有的table view controller之间创建一个联系


在画布中,默认的关联关系是 root view controller。因为table view controller 是导航控制的根视图控制器。进入点之所以指向了导航控制器是因为现在导航控制器现在是table view controller的容器。

现在我们可以注意到在我们的table view 视图上现在多了一栏。这就是我们的导航栏。每个在导航堆里面的控制机都会有一个导航栏,从而可以控制这些控制器向前或者向后导航。下一步我们需要给导航栏田建一个按钮来切换菜品场景。

里程碑:运行app,在我们的table view 上面有了额外的空间,这个就是稻糠控制器提供的导航栏。因为导航栏显示在了最上面,所以状态栏跟我们的内容没有重叠了。

配置导航栏

现在我们需要给菜单列表添加一个标题,和一个导航切换的按钮。导航栏的标题由当前显示的试图控制器决定。因此我们通过使用菜品列表中的项来修改导航栏的标题,而不是直接写死了。

配置菜品列表的导航栏

1、双击菜品列表场景的导航栏

接下来可以输入文字
2、输入Your Meals 然后回车


3、打开对象库(View > Utilities > Show Object Library
4、在对象库中找到 Bar Button Item 对象
5、将这个对象拖到导航栏的右边

6、选中这个栏按钮,打开属性查看器
7、在属性查看器中找到System Item 属性,选择Add
这个时候按钮变成了 + 号


里程碑:运行app。现在导航栏有了一个标题和一个加号按钮。


接下来我们希望当点击 + 号的时候我们能切换到菜品场景,所以我们需要让按钮触发一个切换功能从而调用场景
配置+号按钮
1、打开画布,选择+号
2、使用control + 拖拽将按钮拖到菜品场景

一个Action Segue 的标题会出现在拖拽结束的位置


Action Segue 可以让我们选择当我们点击按钮的时候,从菜品列表场景切换到菜品场景的类型
3、选择 show 
Xcode会设置一个show的切换,从而让菜品场景出现在导航栏控制器里。

里程碑:运行app。当我们点击 + 按钮的时候,导航栏会从菜品列表切换到菜品场景。因为我们使用了导航控制器。所以一个后退的导航已经自动生成了,并且出现在界面上。也就是说我们点击返回的导航就会返回到菜品列表场景


我们使用了show 的切换,因此这个效果是推拉的风格,但这不是我们想要的。推拉导航一般是显示我选中东西更详细的信息的。但是增加一个菜品应该是一个用户执行了一个完成操作,然后返回主场景这么一个操作。所以这样的场景需要 modal segue(模态切换)

我们也不需要删掉现在的切换然而重新创建一个,我们可以在属性查看器改变切换的风格就行了。
改变切换风格
1、选择菜品场景到菜品场景的切换

2、在属性查看器中的Kind中选择 Present Modally
3、在属性查看器中的 Identifier 项里写着 AddItem然后回车

模态试图控制器没有添加到导航堆里面,所以没有导航栏。然是我们可以通过使用一个可视化的连续性来拥有导航栏。为了在 presented modally模式下保证菜品场景也有一个导航栏,我们需要将它和已有的导航控制器进行组合。
给菜品场景添加一个导航控制器
1、点击场景dock、选择菜品场景

2、当我们选中之后点击Editor > Embed In > Navigation Controller.
3、这样Xcode 就会田建一个导航控制器,从而在顶部显示一个导航栏。下一步就是陪着导航栏,并且添加两个按钮。

配置菜品场景的导航栏
1、双击菜品导航栏


2、输入 New Meal 然后回车
3、从对象库中拖拽一个 Bar Button Item 对象放到导航栏左边
4、在属性查看器中的System Item中选择Cancel
5、从对象库中拖拽一个 Bar Button Item 对象放到导航栏左边
6、在属性查看器中的System Item中选择Save

里程碑:运行App。点击 Add 按钮我们就可以切换到菜品场景,但是没有按钮来返回菜品列表场景。接下来我们就是需要给菜品场景中的两个导航按钮添加行为。


使用自动布局完成最终的UI

从我们最初的UI设计到现在我们已经学了很多知识了。尽管我们对于UI做了很多的改动,但是到目前为止,我们不需要做任何更多的布局变动,不过我们需要确保布局中一切元素都是自动布局的。

为了达到这个效果我们需要一些简单的调整

使用 stack view 来更新布局

1、在菜品场景,点击这个淡蓝色的区域来选择stack view

2、在画布的右下方打开 Resolve Auto Layout Issues 按钮


3、点击Update Constraints

元素还是在相同的位置,但是stack view 现在指向了导航视图,而不是最顶上的视图

现在菜品场景约束和 UI如下




里程碑:运行app。文本框、image view 和打分控制器应该离导航栏更近了


在菜品列表中存储一个新的菜品

下一步我们需要实现的功能就是添加一个新菜品。具体来说就是当一个用户在菜品场景输入一个菜品名、打分和照片并点击Save 按钮的时候,我们希望MealViewController 来创建Meal 对象,并且将这些信息传给MealTableViewController从而在菜品列表中进行展示。


首先我们在MealViewController中添加一个 Meal属性

在MealViewController中添加一个Meal 属性

1、打开MealViewController.swift

2、在ratingControl outlet 下 添加下述代码

/*This value is either passed by `MealTableViewController` in `prepareForSegue(_:sender:)`or constructed as part of adding a new meal.*/var meal: Meal?
上述代码声明了一个可选类型的 Meal,也就是可能为nil
我们现在关心的是如何设置这个Meal 并且当点击Save 按钮的时候传输这个对象。为了知道 Save 按钮什么时候被点击了,我们需要在MealViewController.swift中添加一个 outlet

连接 Save 按钮和MealViewController

1、打开Storyboard

2、打开助手编辑器


3、如果需要更多的工作空间,可以折叠左右两个工作区


4、选择Save 按钮

5、使用Control + 拖拽将Save 按钮从画布中拖到代码中的ratingControl属性下。


6、输入saveButton


7、点击Connect

创建一个松弛切换(Unwind Segue)

现在我们的任务是当用户点击保存按钮的时候将Meal对象传输到MealTableViewController,并且当用户点击取消按钮的时候不管这个对象,同时将场景从菜品场景切换到菜品列表场景

为了实现上述功能,我们需要使用一个松弛的切换。一个松弛切换可以寄通过一个一个或多个切换来向后操作,同时返回用户已有视图控制器的实例。我们使用松弛切换来实现反转导航。

当一个切换被触发的时候,我们可以在触发代码中添加我们想执行的代码。这个方法是prepareForSeque,并且在这个方法中,我们也可以存储数据并且在切换的来源视图中做一些必要的清理工作。

在MealViewController中添加prepareForSeque方法

1、返回标准编辑器


2、打开MealViewController.swift

3、在//MARK:Actions 节上添加以下代码

// MARK: Navigation
4、在注视下添加以下代码

// This method lets you configure a view controller before it's presented.override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {}

5、在prepareForSegue方法中添加if语句

if saveButton === sender {}
我们使用了身份确定符号(===)来验证这个对象是不是saveButton outlet 对象
6、在 if 语句中添加以下语句

let name = nameTextField.text ?? ""let photo = photoImageView.imagelet rating = ratingControl.rating

上述代码创建了三个常量。注意两个问号,这个是空合并操作符(nil coalescing operator),目的就是如果name有值则返回值,没有则返回默认值。
7、在 if 语句中添加以下代码

// Set the meal to be passed to MealTableViewController after the unwind segue.meal = Meal(name: name, photo: photo, rating: rating)

上述代码设置了 meal 属性的值

目前 prepareForSegue 方法为:

// 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 MealTableViewController after the unwind segue.        meal = Meal(name: name, photo: photo, rating: rating)    }}
下一步会创建松弛切换从而将数据传给切换目标视图。这个方法必须标记为IBAction 属性,并使用一个segue(UIStoryboardSegue)作为参数。因为我们需要返回菜品列表场景,所以我们需要在MealTableViewController.swift中添加这个方法


我们需要实现的操作是往菜品列表中添加一个新的菜品,并且在视图中创建一个新的行来显示新的菜品

MealTableViewController中添加方法
1、打开MealTableViewController.swift

2、在最后一个花括号前添加以下代码

@IBAction func unwindToMealList(sender: UIStoryboardSegue) {}

3、在方法中添加 if 语句

if let sourceViewController = sender.sourceViewController as? MealViewController, meal = sourceViewController.meal {}
上述 if 语句的条件非常长。

上述代码使用了可选类型转换操作符(optional type cast operator)来将切换的来源视图控制器向下转为MealViewController类型。我们之所以需要向下转型是因为sender.sourceViewController是UIViewController类型,但是我们需要对MealViewController 类型进行操作。

操作符返回的是一个可选值,也就是如果向下转型失败我们会得到一个nil值。如果我们转型成功那么代码将把试图控制器赋值给本地 sourceViewController常量。并且检测是否为空。如果meal属性不为空那么我们将复制给本地的meal。从而执行 if 语句。

4、在 if 语句中添加下述代码

/ Add a new meal.let newIndexPath = NSIndexPath(forRow: meals.count, inSection: 0)

上述代码计算了新的菜品插入的位置,并且通过本地的 newIndexPath进行存储。

5、在 上述代码后添加

meals.append(meal)
6、 在上述代码后添加

tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)

最终方法为:

@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)    }}

现在我们需要创建一个松弛的切换来触发这个方法

将保存按钮和 unwindToMealList 方法绑定

1、打开storyboard

2、使用Control + 拖拽的方式将 保存按钮拖拽到 Exit 项

当释放鼠标之后一个菜单会出现


3、选择 unwindToMealList 方法

现在当我们点击保存按钮的时候,导航按钮会返回菜品列表场景,同时unwindToMealList会被调用

里程碑:运行app。现在点击 + 按钮来创建一个新的菜品,然后点击保存。我们应该可以在菜品列表中看到新添加的菜品

当用户没有输入菜品名的时候让保存按钮不可用

因为在 MealViewController中meal 属性是一个可选的,如果没有名字的话我们的初始化器会返回一个nil,所以我们这个Meal 对象就不会被创建并添加到菜品列表中。只有当用户输入合法的名字的时候我们才让他们可以点击保存按钮

当没有菜品名的时候使保存按钮不可用

1、打开 MealViewController.swif,找到 //MARK UITextFieldDelegate 节

2、添加另一个UITextFieldDelegate 方法

func textFieldDidBeginEditing(textField: UITextField) {    // Disable the Save button while editing.    saveButton.enabled = false}

上述代码会在编辑看是的时候执行,或者当键盘显示的时候。这个代码使得保存按钮不可用

3、再添加一个方法

func checkValidMealName() {    // Disable the Save button if the text field is empty.    let text = nameTextField.text ?? ""    saveButton.enabled = !text.isEmpty}
这是一个助理方法,当文本框为空的时候使保存按钮不可用

4、找到 textFieldDidEndEditing 方法

func textFieldDidEndEditing(textField: UITextField) {}

5、添加以下代码

checkValidMealName()navigationItem.title = textField.text

第一行检测文本框是否有文本了,从而让保存按钮可用,第二行设置场景的标题为输入的文本

6、找到 viewDidLoad 方法

override func viewDidLoad() {    super.viewDidLoad()        // Handle the text field’s user input through delegate callbacks.    nameTextField.delegate = self}

7、添加checkValidMealName方法。

// Enable the Save button only if the text field has a valid Meal name.checkValidMealName()

完整的vieDidLoad方法如下

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()}

里程碑:运行app,但点击 + 号的时候,直到我们输入合法的菜品名并且键盘消失的时候我们的保存按钮才会可用

取消添加一个新的菜品

用户可能会取消添加一个新的菜品,然后回到菜品列表。因此我们需要实现取消按钮的方法

创建和实现取消按钮的动作方法

1、打开storyboard

2、点击助手编辑


3、选择取消按钮

4、使用 Control + 拖拽将取消按钮从画布中拖到代码中


5、在出现的对话框中的Connection中选择Action

6、输入名字cancel

7、类型选择UIBarButtonItem


8、点击Connect

Xcode会添加必要的代码

@IBAction func cancel(sender: UIBarButtonItem) {}

9、在方法中添加

dismissViewControllerAnimated(true, completion: nil)

上述代码是的菜品场景消失,同时菜单列表会出现

最终的cancel代码为

@IBAction func cancel(sender: UIBarButtonItem) {    dismissViewControllerAnimated(true, completion: nil)}

里程碑:运行app,现在点击 + 按钮之后点击取消按钮我们应该返回菜品列表

ps:完整的工程文件目录Download File


0 0