【总结回顾】iOS Apprentice Tutorial 2:
这本书看了几遍也记不清了,总之好多遍,第一遍照着书中的代码敲了一遍,结果还出现了好多bug,删掉重新敲,没有问题之后,又看了一遍,理解一下大体意思,再看的时候稍微自己设计了一个相似的应用,然后利用学到的知识开发出来,功能运行没有问题,就是界面太丑了。
iOS Apprentice系列一共有四本,这是第二本。目前看的这版是第四版,V4.1,升级到了iOS 9、Xcode7和Swif2.0,适合完全没有编程基础的人和初学者来阅读。这本书的可以在RayWenderlich官网注册后订阅Newsletter免费获得:获取地址。需要花钱购买,价格不菲,美国本来书就贵,美元汇率的原因导致随笔买本书就三四百RMB出去了,肉疼。
这本书比起第一本来说,难度上立马上了一个层次,不多看几遍,还真有时候会跟不上节奏。我总感觉需要看上十遍八遍的方能放心。
好了,废话不多说,下面就开始总结一下书中的重点知识。
1. Upside down
在设计App的时候,尽量不使用这个方向。如果你的App支持这个方向,Home按钮可以出现在上方,当用户接到电话时会带来不便,麦克风会在上方离职嘴巴比较远,而不是下面靠近嘴巴的地方。
不过对于iPad应用来说,最好是能够支持全部的四个方向,毕竟人们在使用iPad时,一般没有接电话的需求。(不过类似facetime微信视频之类App在iPad可能需要注意一下麦克风了)
2.UItableView object
这个控件用来展示清单列表。有两种风格: “plain” 和 “grouped”,请见下图(左边是plain风格,右边是grouped风格):
两种风格: “plain” 和 “grouped”
数据是以行的形式展示,一行(row)就是一条数据。
你可以有成千上万行的数据,尽管这种设计模式我们不推荐,大部分用户会讨厌一直往下滑成千上万行才能找到他们想要看到的内容。
UItableView控件使用cell来展示数据。一个cell对应一个row,但是cell和row不完全相同。首先cell是一个view,cell的数量,是由在某一刻,可以看到的row(行)的数量决定的。加入在iPhone屏幕上一次最多能展示10rows,想看到更多row就需要往上滑动屏幕,这时候,这里只有10个cell,尽管实际上可能会有成千行(row)数据。
当一行数据被往上移动移出屏幕不可见后,cell会被重复利用,接着用来展示新出现在屏幕中的那些行数据。下图展示了row和cell的区别:
row和cell的区别
在过去,你需要写好多代码才能复用cell,但是现在Xcode给出了一个非常有用的特性名为prototype cells,可以让你在界面上直接设计cell,然后复用cell。
创建cell的最简单的方法:
1.在storyboard中添加一个prototype cell;
2.在prototype cell上设置它的reuse identifier;
3.调用tableView.dequeueReusableCellWithIdentifier(forIndexPath)
方法
3. The data source(数据来源)
使用UITableView首先需要你把storyboard中的UITableView和对应的.swift文件建立关联,同时也需要遵守data source protocol。
UITableView和对应的.swift文件建立关联最快的方法:
在storyboard中选中UITableView,同时按住Control键,鼠标拖向红点(见下图)
当然了,如果你在创建.swift文件的时候,SubClass选择了UITableViewController,Xcode会自动帮你建立关联(delegate和data source都自动建立)。如果你选择的是普通的UIViewController,那么就需要使用上图中的方法手动建立。
除了鼠标拖动,还有代码方法:
delegate = self
遵守data source protocol
协议中有2个方法是必须有的,其他的方法可选。
下列代码中的2个方法是必须要有的:
override func tableView(tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ChecklistItem", forIndexPath: indexPath)
return cell
}
至于为什么必须要有这两个方法,看下图,下图中UITableViewDataSource是一个协议protocol,我用红色线框和黄色线框标注出来了,红色是public func,黄色是optional public func。
public protocol UITableViewDataSource
综上所述,分为3步吧:
- 表示当前的这个view controller(一般就是.swift文件)是你(UITableView)的data source(数据来源),当我们使用
delegate = self
时,我们就把view controller(视图控制器,以.swift结尾的文件)和UITableView连接起来了。 - 当table view需要知道自己有多少行时,就会发给controller一个“numberOfRowsInSection”的message,来获取当前table view一共有多少行。
- 当table view需要知道自己某一行呈现哪些内容时,就会发给controller一个“cellForRowAtIndexPath” 的message,来获取这当前行cell的数据。
你会在iOS中经常看到这种模式:一个对象代表另外一个对象做一些事情。在这里的例子中,ChecklistViewController是给table view提供数据,不过只有在table view请求的时候才会提供。
4. Return语法
在上面的代码中,numberOfRowsInSection方法中返回了数值1,表示当前table view只有一行数据。
return语法在swift中非常重要,return允许一个方法能够将数据返回给方法的调用者(caller)。在这里的例子中,调用者(caller)就是UITableView对象,UITableView对象想知道table里有多少行。
返回的数值经常被叫做是方法的结果。
5. 调用次数(重点!重点!!重点!!!)
当我们使用tableView(numberOfRowsInSection)
方法时,如果返回值是5(return 5),那么实际上你是在让table view显示5行。
同时,table view会把“cellForRowAtIndexPat”消息发送5次,一行一次。
如果不理解这一点,在后面使用
tableView(cellForRowAtIndexPath)
方法时,容易出错或者不知所措~
6. Index paths讲解
什么是index path?
首先NSIndexPath是一个对象(object),这个对象用来指出table中某个具体的行(row),NSIndexPath里面包含了row number(行数)和section number(区域数),仅此两个。当tableview需要获取某个cell的数据data source时,你可以通过indexPath.row
属性查找row number,然后找出当前cell到底想获取哪行row的数据。
table也可以将rows行分布到不同的section中,例如在手机通讯录中,可以通过姓氏来排列组合,所有的同一姓氏联系人为一个section。
想要找出某row属于哪个section,可以使用indexPath.section
属性。
顺便说一下,NSIndexPath的前缀NS是NextStep的缩写,带有NS前缀的object都是由Foundation框架提供的。NextStep是一个操作系统,是Mac OS X 和iOS的前身。
7. Tag
Tag就相当于是用数字做的标识符(identifier),我们之前使用的identifier都是自己输入的字符串,Tag相当于是用数字做identifier,这样使controller(.swift文件)能够知道自己控制的是storyboard上哪个控件。Tag的默认值是0,所以当你使用Tag时,数字要大于0,
为什么在用cell时要用tag呢?我们之前学会的control拖动法建立Outlet连接在这里为什么不能用?
回答:有时候table上有很多个cell,每个cell都有自己的label等控件,如果我们使用Control拖动法建立outlet连接,这个outlet只是表示当前cell的label,而table上有很多个cell。而且label控件是输入cell的,不是输入View controller,所以不能使用outlet。
我觉得这里有必要附上英文原文,以防我理解有误,引起误会:
Answer: There will be more than one cell in the table and each cell will have its ownlabel. If you connected the label from the prototype cell to an outlet on the view controller, that outlet could only refer to the label from one of these cells, not all of them. Since the label belongs to the cell and not to the view controller as a whole,you can’t make an outlet for it on the view controller. Confused? Don’t worry aboutif for now.
8. 小知识点
- 从零开始,不是从一开始。
计算机计算都是从零开始的,第一个数值是0,第二个数值是2,在Array类型中最常见。如果数组中有4个数字,那么序列就是0,1,2,3。一开始可能不太习惯,毕竟我们平时都是从一开始数数的,不过这就是电脑数数的方式,习惯就好。 - 取模运算,求余数,符号是%。
9. 奇怪的崩溃
如果你的程序奇怪的崩溃了,首先你要确认一下你是否不小心在代码中设置了一个断点(breakpoint)。断点是一种调试代码的工具,能让你的程序在执行到某行代码时停止编译,同时显示Xcode的debugger窗口,看起来像是程序崩溃,其实只是程序停止执行。
断点就是下图中左边边界上的蓝色箭头,箭头方向朝右:
断点 breakpoint10. 用户体验的小细节(细节决定成败)
当你点击某行cell后,这行会显示灰色。然后灰色就会一直存在,直到你点击另外一行,另外一行变灰色。如果想在点击时灰色,手松开后灰色就消失不见,可以使用table view中的delegate中的方案来实现。
我们之前了解到,cell的显示内容也就是数据由data source控制,那么用户点击cell或者和cell的其他手势交互则由delegate控制。
11. 委托模式 The delegation pattern
在iOS中,delegation的概念非常常见。一个object常常借助另外一个object来实现某个任务,这样做的好处很多,因为每个object只需要做自己最擅长的事情,然后让其他的object做它们更擅长的事情,这种理念在table view中得到了很好的提现。
因为不同的App对于自己的数据有不同的要求,所有table view必须具有处理各种不同类型数据的能力。为了不让table view过于复杂,UIKit的设计者选择委托其他对象(也就是data source)来完成填充cell显示内容这项任务。
table view不关心谁是它的data source,也不关心目前处理的数据是何种类型,只要这个data source能够把消息发送给cellForRowAtIndexPath,table view收到一个cell作为返回值,就行了。这种模式能够让table view更轻盈,table view把处理数据的责任转给了:你的代码。
同样的,table view也能识别出用户点击了某行row,但是之后要做出哪些事情来回应用户的点击行为呢?在我们这本书checklist应用中,点击之后是出现一个对勾,但是其他的App会有不同的反应。
使用委托delegate,table view需要做的只是发送消息而已,比如消息是:一个点击事件发生了,然后让delegate处理之后的事情。
override func tableView(tableView: UITableView,didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
通常情况下只需要一个delegate就可以了,但是table view有两个delegate:UITableViewDataSource处理数据,UITableViewDelegate处理点击事情和其他事件。
12. tableView.cellForRowAtIndexPath()
和tableView(cellForRowAtIndexPath)
区别
override func tableView(tableView: UITableView,didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if cell.accessoryType == .None {
cell.accessoryType = .Checkmark
} else {
cell.accessoryType = .None
}
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)}
获取被点击的Row的NSIndexPath
if let cell = tableView.cellForRowAtIndexPath(indexPath)为了找到这行cell,你需要调用tableView.cellForRowAtIndexPath()
方法,你需要意识到,这和之前在data source中方法tableView(cellForRowAtIndexPath)
不是一回事,这点很重要。尽管从方法名字上看起来挺像的,但是这两个方法是不同对象中的不同的方法,达成不同的任务。这看起来很容易让人混淆,对吧?
data source中tableView(cellForRowAtIndexPath)
的方法是创建或者复用cell,你永远不会调用这个方法,只有UITableView可以调用data source方法。
而tableView.cellForRowAtIndexPath()
返回的是cell对象,但是这个cell是当前被展示在屏幕上的某一行(row)所在的cell。tableView.cellForRowAtIndexPath()
不会创建新的cell。如果当前界面上没有所需的cell,就会返回一个nil值。
记得我在之前的章节中说过,方法的命名要简洁明确有描述作用吗?UIKit在这方面做的一直不错,但是这两个方法是一个特例,两个相同的名字用在了不同的地方,这样会给开发者带来困惑和麻烦。要小心这个坑 ,别跳进去了!
13. UITableViewController的优势
在这图中可以看到,data Source和delegate都已经连接到对应的view controller中了,这是 UITableViewController的标准设置,如果你使用的是 UIViewController,就需要手作关联data source和delegate了。
关联data source和delegate