这一只是我的一个座右铭-集中和简单.简单,可以比实现复杂的东西更难.你必须花很多力气去让你的思维变得简单,有条理.但最终它的价值非常大,因为一旦你到达了那一步,你就可以撼动山脉.
-Steve Jobs
现在你们已经对我们的demo app原型设计有了一个基本的认识,这一章我们将继续做一些更有兴趣的事然后用UITableView来构建一个简单的基于表的app.一旦你掌握了这个技术和表格视图定制(我们将在下一章讨论),我们将开始构建Food Pin app(暂时我还不知道这是什么软件.)
首先,在iPhone app里什么是正确的表格视图(table view)?表格视图(table view)在大多数iOS apps里是一个常见的UI元素.大多数apps(除了游戏),利用表格视图来显示内容.最好的例子是内置的电话app.你的联系人都显示在一个表格视图(table view.下文都直接用table view)里.另一个例子是邮件app.它用表格视图(table view)来显示你的邮箱和电子邮件.table view不仅仅只为清单设计文本数据,它也允许你在图片的形式里显示数据.TED,Google+和Airbnb也同样是个好例子.下图显示了少量基于表格的apps的例子.尽管他们看起来不一样,但是基本上都用到了table view.
创建一个SimpleTable Project
不要光看书不练习.如果你把学习iOS编程看成一件严肃的事,停止阅读.打开你的Xcode然后写代码!这是学习编程最好的办法.
让我妈开始创建一个简单的app.这个app真的很简单.我们仅仅在一个简单的table view里演示一下餐厅的清单.我们将在下一章改进它.如果你没有放弃Xcode,登陆它然后用”Single View application"模板创建一个新的工程.
点击”Next”.为Xcode工程填写所有要求的选项:
Product Name:SimpleTable - 这是你app的名字.
Organization Name:AppCoda - 这是你公司的名字
Organization Identifier: com.appcoda - 事实上这个域名写反了.如果你有一个域名,你可以用你自己的域名*名字.否则,你可以用"com.appcoda"或者就填"edu.self".
Bundle Identifier: com.appcoda.StackViewDemo - 这是你app唯一的标志,用于提交app.你不需要填这个选项.Xcode会自动帮你生成.
language:Swift - 我们将用Swift来开发这个工程.
Devices: Universal - 选择"Universal".通用app是为iPhone,iPod touch,和ipad设备优化过的单独app.在这个demo,我们将设计一个用户界面能运行在所有的设备上.
Use Core Data:[unchecked] - 不要选择这个.这个简单工程用不到Core Data.
Include Unit Tests:[unchecked] - 不要选择这个.这个简单工程用不到单元测试.
Include UI Tests:[unchecked] - 不要选择这个.这个简单工程用不到UI测试.
点击"Next".Xcode会问你准备把StackViewDemo工程保存在哪里.在你的Mac上选择一个文件夹,点击"Create”.
用户界面设计
在一个iOS app里呈现一个表格的数据,所有你需要用到的是table view对象.首先,选择Main.storyboard来切换到界面编辑器.在对象库里,找到Table View对象把它拖到视图里.
选择table view.在Attributes inspector(如果它没有出现在你的Xcode,选择View>Utilities>Show Attributes Inspector)里,把Prototype Cells的数字从0改成1.一个原型单元格(prototype cell)将会出现在table view里.Prototype cells允许你来简单的设计table view单元格的布局.它同样带来了一些标准的单元格样式包括basic,right detail,left detail和subtitle来让你选择.在这里例子里,我们只用basic样式.关于自定义表格,我会把它留到下一章讲解.
选择单元格然后打开Attributes inspector.把cell样式(Style)改成Basic.这个样式用来显示文字和图片都有的单元格很好用.此外,把identifier设置成Cell.这是识别prototype cell独一无二的键.我们将在之后的代码里用到它.
在不使用任何代码的情况下运行你的app
试着在模拟器里运行你的app.点击”Run”按钮来构建并测试你的app.模拟器的屏幕将会看起来像下面的图一样
很简单对吗?你已经为你的app创建了table view.同时,它没有显示任何的数据.如果你仔细看看table view,会发现它并没有完美的拉伸到整个屏幕.如果你彻底的理解了auto layout,我相信你应该知道原因.
到现在为止我们还没有为table view定义任何布局约束.这就是为什么它看起来有些奇怪.现在在界面编辑器里选择table view,在布局栏里点击Pin按钮.设置上下左右的间距.
选择每一个红色虚线的标志.一旦选好了,红色虚线会变成红色实线.点击”Add 4 Constraints”按钮来添加约束.这里我们为table view的每条边定义4个间隔约束.在这里,我们确保UITableView的地步和Bottom Layout Guide之间没有间距.两个水平间距约束确保table view的左右两边将拉伸到视图的边缘.换句话说,你的table view将自动调整大小来填满整个显示设备.
你可以再运行一次这个工程.table view现在应该支持所有的屏幕大小.界面设计好以后,我们将开始进到核心部分然后写一些代码插入到table data.
UITableView和Protocols
我之前提到过我们处理基础类是由iOS SDK提供的.这些类被组织在一起叫做”frameworks”.UIKit框架就是一个最常用的框架.
它提供类来构建和管理你app的用户界面.界面编辑器对象库里所列的所有对象都是framework提供的.你在HelloWorld app里用到的Button对象,和我们现在用到的Table View对象都是来自UIKit framework.当我们使用工具Table View的时候,真正的类是UITableView.简单的说,对象库里的所有的UI组件都有对应的类.你可以在对象库里点击任何工具然后会在pop-over菜单显示真正的类名.
我故意把类和方法的讨论留到以后的章节.如果你不能理解类,不要担心,把它想成一个代码模型就好了.我会在以后的章节里给你解释它.
现在你又了一点关于Table View和UITableView之间关系的概念.我们将写一些代码来插入表格数据.在工程导航里选择ViewController.swfit来在编辑框里打开文件.在UIViewController后面添加UITableViewDataSource,UITableViewDelegate来采用协议.
当你在UIViewController之后插入代码时,Xcode查出一个错误.这个红色的感叹号标记表明这里有个错误.点击编辑器左边小的感叹号标记,Xcode将高亮代码行,然后显示一条信息告诉你错误细节.这条信息显示了问题的原因,但是它不会告诉你解决方案.
所以,”Type ViewController does not conform to protocol UITableViewDataSource”是什么意思?
在Swift里,UITableViewDelegate和UITableViewDataSource是已知的接口.为了在table view里显示数据,我们必须制定一套规则并在协议里定义.这里,ViewController类是一个采用了协议,并且执行了所有强制的方法.
这可能让人有点晕头转向.这些协议是什么?为什么要用协议?
好吧,我们假设你正在开始一个新的生意,你顾了一个平面设计师来设计你公司的logo.他是一个熟练的设计师,有能力创作任何logo.但是他不能马上开始logo设计.最少,你需要提供它一些要求比如公司的名字,颜色倾向,商业性质,这样他才能开始创作一个logo.但是,你很忙,你把这个任务委托给你的私人助手,然后让她联系设计师,提供logo要求给他.
在iOS编程里,UITableView类就像平面设计师.它足够灵活来在表格形式里显示各种数据(如图像,文字).你可以用他来显示国家或者联系人姓名清单.在我们工程里,我们将用缩略图展示餐馆的清单.
但是在UITableView能够为你显示数据之前,它要求有人提供一些基本的信息比如:
在table view里你想要显示多少行?
表格数据是什么?例如,你要在第二行显示什么?你要在第五行显示什么?
“someone”被称作委托对象(delegate object).在上面的类比里,私人助手就是委托对象.在iOS编程里,它同样应用委托概念通常叫做delegation pattern.一个对象依赖于另一个对象来执行一个特殊的任务.在我们工程里,ViewController就是提供表格数据的代表.下图阐明了UITableView,协议和委托对象之间的关系.
你怎样告诉UITableView要显示什么数据?UITableViewDataSource协议是关键.它是你的数据和table view的链接.协议定义了你需要添加的方法清单,这里有两个对UITableViewDataSource来说必须的方法:
tableViwe(_:numberOfRowsInSection:)
tableView(_:cellForRowAtIndexPath:)
你需要做的是提供一个对象来实现上面的方法,让UITableView知道显示哪一行和每一行的数据.协议也能定义一些可选的方法,不过我们不准备在这讨论.
在另一方面,UITableViewDelegate协议,处理了UITableViwe的外观?所有的方法在协议里定义都是可选的.它们让你管理表格行的高度,配置章节的页眉和页脚,重新排序单元格,等等.在这章我们不改变任何方法,我们把那些留在后面的章节.
有了一些协议的基本知识,我们继续写这个app的代码.选择ViewController.swift然后给表格数据定义一个变量.给这个变量起名restaurantNames然后插入下面的代码:
var restaurantNames = ["Cafe Deadend", "Homei", "Teakha", "Cafe Loisl", "Petite Oyster", "For Kee Restaurant", "Po's Atelier", "Bourke Street Bakery", "Haigh's Chocolate", "Palomino Espresso", "Upstate", "Traif", "Graham Avenue Meats And Deli", "Waffle & Wolf", "Five Leaves", "Cafe Lore", "Confessional", "Barrafina", "Donostia", "Royal Oak", "CASK Pub and Kitchen”]
在这个例子中,我们使用一个数组来储存表格数据.在Swift声明一个数组的语法比OC简单多了.给这些值加上双引号然后用逗号隔开就可以了.
在Swift里,用let声明常量用var声明变量.和OC相比非常简单.一个数组里所有的项都是同类型的(如:String).感谢Swift类型推断的特点吧,你都不需要指定数组的类型.它会自动的被Swift检测出来.Swift能够推断出来restaurantName变量是一个String类型.
####给纯新手介绍下数组
数组在计算机编程里是一个基本的数据结构.你可以把一个数组想象成数据元素的集合.上面代码里的restaurantNames数组,代表着一个字符串(String)元素的集合.你可以把数组想象成这样:
![QQ20151209-1@2x.png](http://upload-images.jianshu.io/upload_images/1151205-a90f48a19f1b8e70.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
每一个数组元素都可以由索引确定或者访问.有10个元素的数组将有0到9的索引.这意味着restaurantNames[0]返回的是数组的第一项.
我们继续码字.我们给UITableViewDataSource协议添加两个必须的方法.
> func tableView(tableView: UITableView, numberofRowsInSection section: Int) ->INT
{
// 在章节里返回行数
return restaurantNames.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = “Cell”
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
// 确认单元格...
cell.textLabel?.text = restaurantNames[indexPath.row]
return cell
}
第一个方法用来制定table view里一个章节的总行数(table view 可以有多个部分但是只有一个默认的.)你可以简单的调用count方法来得到restaurantNames数组里物品的数字.
第二个方法叫做每次显示一行.使用indexPath对象,我们能得到当下的行数(indexPath.row).这里我们做的是从restaurantNames数组里检索索引项,然后把它分配给文本标签(cell.textLabel.text)来显示.
okay,但是第二行代码的dequeueReusableCellWithIdentifier是什么?
这个dequeueReusableCellWithIdentifier方法用来从一个有着特殊单元格标识符的队列里检索一个可重复使用的单元格.这个单元格标识符是我们之前在界面编辑器里定义的.
你想要你基于表格的app反应快,即使处理数千行数据也有求必应.如果你给你一行数据分配一个新的单元格而不是重复利用一个,你的app将使用过多的内存,并可能导致当用户滚动table view时感觉很迟钝.记住每一次单元格的分配都是一次性能消耗.尤其是当分配发生在一个很短的周期之类.
iPhone屏幕的真实空间是有限的.即使你的app需要显示1000条记录,屏幕可能最多只能填10个单元格.因此,究竟为什么要分配上千个图标单元格而不是创建10个单元格然后重复利用它们呢?这将节约成吨的内存然后让表格视图更有效率的工作.考虑到性能的原因,你应该重复利用表格视图单元格来替换创建新的单元格.
![QQ20151209-2@2x.png](http://upload-images.jianshu.io/upload_images/1151205-e3c858a78b800e81.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
现在,我们来点击”Run”按钮来测试你的app.哎哟!这个app依然跟以前一样显示一个空白的表格视图.
为什么table view不像我们希望的那样显示内容呢?我们已经为显示表格数据写了代码然后实施了所有必须的要求.但是为什么table view不像我们希望的来显示内容呢?
有一件事被遗忘了.
#### 连接DataSource和Delegate
尽管ViewController类已经添加到UITableViewDelegate和UITableViewDataSource协议里,但是storyboard里的UITableView对象不知道它.我们应该告诉UITableView对象ViewController是数据源的委托对象.
回到Main.storyboard.选择table view.按住control键,拖曳table view到文件大纲里的View Controller对象.
![QQ20151209-3@2x.png](http://upload-images.jianshu.io/upload_images/1151205-0304346e5ada4fc2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这就对了.确保他们的联系正确链接,你可以再次选择Table View.点击多功能区域里的Connections Inspector图标.或者,你可以右击表格来显示联系.
![QQ20151209-4@2x.png](http://upload-images.jianshu.io/upload_images/1151205-e346c35a8f1a1a89.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#### 测试你的app
点击”Run”然后在模拟器里读取你的app.你的app现在应该会显示一个餐馆清单.
![QQ20151209-5@2x.png](http://upload-images.jianshu.io/upload_images/1151205-3355f63f7c108ad7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####为Table View添加缩略图
给每一行添加一个图片是不是很好呢?UITableView做这个很简单.你仅仅需要添加一行代码来给每一行插入缩略图.
从Finder拖动图片到Assets.xcassets
![QQ20151209-6@2x.png](http://upload-images.jianshu.io/upload_images/1151205-5cc75a0fd62caa03.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
现在编辑ViewController.Swift然后添加下面这行代码到tableView(_:cellForRowAtIndexPath:)方法.把它放在return cell:cell.imageView?.image = UIImage(named: “restaurant”)之前.
UIKit框架提供的UIImage类让你从文件里创作图片.它支持各种图片格式例如PNG,GIF和JPEG.去掉图片的名字(文件扩展是可选的)然后这个类会读取图片.
再一次调取我们用过的Basic单元格格式,它带来一个默认的区域来展示图片或者缩略图.这条代码介绍UITableView来读取图片然后在单元格的图片视图里显示它.现在,点击”Run”按钮你的SimpleTable app应该会在每一行显示图片.
![QQ20151209-7@2x.png](http://upload-images.jianshu.io/upload_images/1151205-8599aa5ae6e6a1d2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#### 隐藏状态栏
table view的内容与状态栏重叠了.这看起来不太好.一个简单补救办法是隐藏状态栏.你可以在每个视图控制器的基础上控制状态栏的显示.如果你不想在一个特殊的视图控制器显示状态栏,简单的添加下面的代码行:
> override func prefersStatusBarHidden() -> Bool {
return true
}
插入这行代码到ViewController.swift然后测试app.你可以看到一个没有状态栏的全屏表格视图.