在清单中添加新的项目
目前为止,你的列表中仅包含几个固定的行,但是我们这个app的目的是可以让用户创建属于自己的列表。因此,你需要给予用户自己添加项目的能力。
在这一小节你要增加一个叫做navigation bar(导航bar)的东西到app的顶部。这个bar有一个蓝色的“➕”按钮,点击后可以打开一个新的界面,使你可以为新的项目输入名称。
但你点击“Done(完成)”按钮后,新的项目会被增加到列表里。
展现一个新的界面用于添加项目是许多app的一个基本套路。一旦你学会这个技巧,你会离成熟的iOS开发者更进一步。
以下是你在这一节中要做的事情。
1、添加一个navigation controller(导航控制器)
2、将Add按钮放入navigation bar
3、当你点击Add按钮时,伪造一些数据进去
4、通过滑动删除一行
5、在添加项目的界面中使用户可以输入新项目的名称
一如既往的,我们先将这些功能分解成小的步骤。当你将Add按钮放入界面后,你先在代码里伪造一条数据添加到列表中。与一次性写完添加项目的代码不同,我们会假装很多东西已经存在了,仅仅是处理一小部分内容,然后一点点的扩充它。
当你伪造的数据可以成功的添加到列表中后,我们就可以开始真正把添加新项目这个界面做完了。
Navigation Controllers(导航控制器)
首先我们来添加这个navgation bar。你也许在对象库(Object Library)中见过一个叫做Navigation Bar的东西。你可以把它拖出来,并且放置在屏幕的顶端。不过,我们这次不打算这样做。
取而代之的是,你会用嵌入的方式将view controller添加到navigation controller内。
在iOS用户界面组件中,navigation controller(导航控制器)的使用频率可能是仅次于table view的。它是一种使你可以一个界面转到另一个界面的东西:
UINavigationController会为你照顾绝大多数关于navigation bar的内容,这极大的提高了你的编程效率。navigation bar的中间有一个标题,和左边的一个“back(返回)”按钮用户自动将用户带回之前的界面。你可以在右边放入一个自定义的按钮。
添加导航控制器是非常容易的。
打开Main.storyboard并且选择Checklist View Controller(黄色图标那个)。
在Xcode顶部的菜单中,选择Editor -> Embed In -> Navigation Controller。
这样,界面建造器就添加了一个新的Navgation Controller场景并且将自己和view controller联系起来。
当app启动时,Checklist View Controller自动被放入Navigation Controller中。
运行app试试看。
app在外观上与之前唯一的不同就是顶部有了一个navigation bar。感谢这件事,状态栏不再会覆盖列表第一行的一部分了。
回到storyboard并且双击Checklist View Controller中的navigation bar来编辑navigation bar的标题(你需要双击大概是navigation bar的中间的位置,否则不会出现编辑内容,可以参考下面图示的位置)。
将标题修改为Checklists。
你现在所改变的,就是你选择Embed In命令后自动添加的Navigation Item的对象。
这个Navigation Item对象包含标题及按钮,当view controller被激活时它们就会展现在navigation bar中。每一个嵌入式的视图都有它自己的Navigation Item用于配置在navigation bar显示什么内容。
当navigation controller滑动一个新的view controller到屏幕上时,它会用这个新的view controller的Navigation Item代替原有navigation bar上的内容。
从对象库中拖出一个Bar Button Item到navigation bar的右边。
确认使用的是Checklist View Controller的navigation bar,而不是navigation controller!
这个新按钮默认的名称叫做“Item”,但是我们想要的是一个➕号。
选定这个按钮,打开它的属性检查器,将System Item选项更改为Add。
如果你看一下System Item中的选项,你会看到这里有许多预置的按钮类型:Add、Compose、Reply、Camera、等等。你可以在你自己的app中按照它们的含义使用它们。
例如,你不能在用于发送邮件的按钮上使用一个摄像机图标,不恰当的使用这些图标会使你的app在审核时遇到极大的麻烦。
OK,我们又有了一个按钮。如果你运行app,你会看到如下界面:
当然,点击这个什么都不会发生,因为你还没有将它和动作方法连接起来。在接下来的短暂时间内,你会创建一个新的界面,就是“Add Item(添加新项目)”界面,并且通过点击这个➕号按钮跳转到这个界面。但是在这之前,你首先要学习如何在列表中添加新的一行。
我们来把这个➕号按钮连接到一个动作方法上。在这方面,你已经有了十足的经验,我们前一个课程中多次有过这种操作,所以你应该很轻车熟路。
打开ChecklistViewController.swift,添加动作方法:
@IBAction func addItem() {
}
暂时我们不用添加具体的内容进去,只需要将它和➕按钮连接起来。
打开storyboard,按住ctrl将➕按钮拖拽到Checklist View Controller上,如下图所示:
或者,有个更加简单的办法,就是按住ctrl拖拽➕按钮到离你很近的一个黄色圆圈图标上,见下图:
实际上你可以拖拽按钮到任何代表这个view controller的地方,而上面的方法,是最好的选择。
放开鼠标后会弹出一个窗口,选择Sent Actions分节下的addItem:
让我们来给addItem()添加点内容,回到ChecklistViewController.swift,在这个方法中添加以下代码:
@IBAction func addItem() {
let newRowIndex = items.count
let item = ChecklistItem()
item.text = "I am a new row"
item.checked = false
items.append(item)
let indexPath = IndexPath(row: newRowIndex,section: 0)
let indexPaths = [indexPath]
tableView.insertRows(at: indexPaths, with: .automatic)
}
在这个方法内部你创建了一个新的Checklistitem对象,并且将它添加到数据模型(就是items数组)中。你同时也告诉table view,“我插入了一个新的行,你要更新一下数据”
我们来讲讲上面代码的作用:
let newRowindex = items.count
你需要计算这个新添加的行在数组中的索引(index)编号。为了使table view能正确的更新新的一行,这个计算是必须的。
当app启动时,数组中有5条数据,并且屏幕上也相应的显示出5行。这5行的index分别是0,1,2,3,4,电脑是从0开始计数的,记得吗?所以新增到数据中的数据它的index必然是5(itmes里有5条数据,所以items.count=5,count就是计算数组中元素的个数)。
换而言之,当你新增一行到table view中时,新的一行的indexpath一定和数组中的index是相等的。
你将新的一行的index放到一个局部常量newRowIndex中。因为它不会被变更,所以用作常量。
接下来的几行,你就应该比较熟悉了:
let item = ChecklistItem()
item.text = "I am a new row"
item.checked = false
items.append(item)
我们之前在init?(coder)里见过他们。他们创建了一个新的ChecklistItem对象,并且将这个新的对象添加到数组中。
现在数据模型中有6个ChecklistItem对象了,它们都在items数组中。注意一下,此时newRowIndex仍然是5,而items.count已经是6了。这就是为什么你需要在添加新的一行前,先计算items.count的值并且存储到newRowIndex中了。
只把新的ChecklistItem对象添加到数据模型的数组中是不够的。你同时还要告诉table view把这个新的数据添加到新的cell上去,然后在一个新的行里展现它。
let indexPath = IndexPath(row: newRowIndex,section: 0)
和你之前知道的一样,table view使用index-path来标示行号,所以首先你要使用一个IndexPath对象来标示这个新的行,这里用的就是newRowIndex的值。所以新的一行的index-path就是5。
接下来的一行创建了一个临时的数组,用来保存刚才生成的indexPath:
let indexPaths = [indexPath]
你需要使用insertRows(at:with:)这个方法来告诉table view添加新的一行,但是注意这个方法的名称Rows是复数,这意味着其实你可以通过这个方法,一次性添加许多行。
所以它的参数并不是一个单独的IndexPath,而是一个包含index-path的数组。幸运的是创建一个包含index-path的数组是非常简单的,我们只需要使用[]这对方括号就可以了,这对方括号的作用就是创建一个新的数组,而数组的类型就和方括号中的类型一致,这里我们就写作[indexPath],这样就是一个包含indexpath对象的数组了。
最后,你通知table view插入新的这一行,这里的“with: .automatic”参数的作用是:使table view插入新行时,闪现一个漂亮的小动画。
tableView.insertRows(at: indexPaths, with: .automatic)
复习一下整个过程:
1、创建一个新的ChecklistItem对象
2、将这个新创建的对象添加到数据模型中
3、为它在table view插入一个新的cell
其实你也可以添加多行到表格中,可以自己试试。这些新的行当你点击它们时,也可以触发对勾符号的开关。而且不论你怎么上下滚动,对勾符号的状态都会保持一致。
记住,你一定要同时添加数据模型和表格。当你发送insertRows(at: with:)到table view时,你的意思是:“嗨,表格,我的数据模型中有一些新的数据要你添加进去”
这是非常重要的!如果你忘记了告诉table view这里有新的数据,或者你告诉table view这里有新的数据,但是实际上你没有将这些数据添加到数据模型中的话,你的app就会挂掉。数据模型与视图必须保持同步。
练习:给新增加的行默认对勾符号的状态。
删除行
也许我们该给用户删除某些行的权利。
在iOS app中通常的操作方法是“滑动删除”。你用手指将某一行从右往左滑,然后在在最后边就会出现一个删除按钮。点击一下这个按钮就可以完成删除,如果点击了其他地方则取消这次操作。
这种滑动删除是非常容易做的。
在ChecklistViewController.swift中添加下面这个方法,我建议把这个方法放在table view方法附近。
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
items.remove(at: indexPath.row)
let indexPaths = [indexPath]
tableView.deleteRows(at: indexPaths, with: .automatic)
}
当你在你的视图控制器中使用commitEditingStyle方法后,table view会自动激活滑动删除功能。你要做的全部事情就是:
1、从数据模型中移除掉这条数据。
2、删除table view中对应的行。
它和addItem()做的事情正好相反。你再一次使用了一个临时数组保存index-path对象并且告诉table view删除掉这一行。
运行app,试试效果。
⚠️:释放对象
当你操作item.remove(at:)时,并不仅仅是从Checklistitem中取出了这个值,而是永久的将它破坏掉了。
我们会在下一个课程中详细的讨论这件事,在这里我们只需要记住如果一个对象没有任何引用,那么这个对象会被自动销毁掉。当ChecklistItem对象进入数组时,这个数组就引用了它。
但是当你把ChecklistItem对象从数组里取出来时,这个引用就不存在了,并且这个对象也就被销毁了。或者,以计算机的语言来讲,它被释放了。
一个对象被销毁了意味着什么?每一个对象都占用计算机中的一点内存。当你创建一个对象的实例的时候,一块内存就会被预占,用于保存这个对象的数据。
如果这个对象被释放了,那么这块内存也就被释放了,可以用来存储其他东西。被释放掉的对象不再存在,并且再也无法被使用。
在早起的iOS版本中,你必须手动释放这些内存。幸运的是,现在时代变得很快,Swift使用一种叫做自动引用计数(Automatic Reference Counting,简写为ARC)的机制来管理对象的生命周期,对你而言无须在记住哪里需要释放它们。对我而言,不用去操心释放内存,简直太棒了!