原文:watchOS 2 Tutorial Part 2: Tables
欢迎回到 watchOS 2 系列教程!
在第一部分,你通过开发第一个界面控制器,学习了 watchOS 2 开发的基础知识。
在教程的第二部分,会向你的 app 添加一个 table 来展示航班列表。
在这个过程中,你会学到:
- 如何添加一个新的界面控制器,往控制器中添加一个 table,并且设计原型行。
- 如何创建 WKInterfaceController 的子类来填充这个列表,配置每一行的数据,并且处理选中事件;
- 如何模态呈现界面控制器和向它传递数据来显示。
介绍了这些,让我们正式开始吧!
开始
打开 Watch\Interface.storyboard,从对象库中拖动另一个界面控制器到 storyboard 画板中,放在已经存在的航班控制器的左边。
选中新的界面控制器,打开属性检查器然后做如下修改:
- 设置 Identifier 为 Schedule;
- 设置 Title 为 Air Aber;
- 勾选 Is Inital Controller;
- 勾选 Display Activity Indicator When Loading
你会注意到截图的标题是深灰色的,而不是充满活力的粉红色。让我们解决它。
打开文件检查器然后改变 Globla Tint 为 #FA114F。现在看起来更好一点了:
下一步,从对象库中拖动一个 Table 到这个新的界面控制器中:
在文本大纲中选中 Table Row Controller:
使用属性检查器设置它的 Identifier 为 FlightRow。使用 identifier 作为行的类型标示来通知列表哪一行应该被实例化,它非常重要,所以需要你去设置。
设计行界面
首先修改行提供的默认布局。从文档大纲中选择 table row 中的组,使用属性检查器设置组的 Spacing 为6、Height 为 Size To Fit Content。
table row 默认有个标准、固定的高度。然而,大多数时候你会希望能显示全部添加进去的界面元素,所以总是值得使用这种方式去修改高度属性。
将一个分隔线从对象库中拖到行中。你不会真的用它去分割什么,而是向你的行里添加一点视觉上的间隔。选中分割线,使用属性检查器做如下修改:
- 设置 Color 为 #FA114F;
- 设置 Vertical alignment 为 Center;
- 设置 Height 为 Relative to Container;
- 设置 Adjustment 为-4。
最后检查器应该是这样:
现在是时候填充这一行了!
从对象库中拖一个Group到到 table row 中,放在分割线的后面。选中组,在属性检查器中修改如下属性:
- 设置 Layout 为 Vertical;
- 设置 Spacing 为0;
- 设置 Width 为 Size To Fit Content。
你可能注意到你经常设置的 Spacing 属性;它的作用仅仅是收紧组中的界面元素之间的间距让它们在小屏幕上看起来更清晰。
拖动另一个 Group 到刚才添加的组中,做如下改变:
- 设置 Spacing 为4;
- 设置 Height 为 Fixed,值为32。
往这个新的组中,添加一个 Label、一个 Image然后另一个 Label。这两个标签会显示每个航班的起点和重点。
现在你需要往 image 中添加图片。下载图片然后把它添加到 Watch\Assets.xcassets 中。这次创建一个新的叫做Plane的图片,放在2x槽中:
重新打开 Watch\Interface.storyboard 然后选择这个 image。使用属性检查器,做如下改变:
- 设置 Image 为 Plane;
- 设置 Tint 为 #FA114F;
- 设置 Vertical alignment 为 Center;
- 设置 Width 为 Fixed,值为24;
- 设置 Height 为 Fixed,值为20。
选择左边的标签设置它的文本为 MAN。修改它的 Font 为 System, style 为 Semibold 和20的字体大小。最后设置它的 Vertical alignment 为 Center。
同样修改右边的标签,但是文本修改成 SFO。你的 table row 现在应该是这样:
界面元素层次结构像下面这样:
你已经完成大多数 table row 界面的设计;之后需要增加航班号和状态了。
从对象库中拖动另一个 Group 到 table row ,确保它是包含起点终点标签的那个组的子节点:
当你继续设计这个界面,你可以看到更多使用嵌套组与混合布局来创建复杂布局的例子。根本就没自动布局的事。
拖动两个 label 到新的组中。再次使用属性检查器对最左边的标签做如下的改变:
- 设置 Text 为 AA123;
- 设置 Text Color 为 Light Gray Color;
- 设置 Font 为 Caption 2;
- 设置 Vertical alignment 为 Bottom。
修改右边的标签:
- 设置 Text 为 On time;
- 设置 Text Color 为 #04DE71;
- 设置 Font 为 Caption 2;
- 设置 Horizontal alignment 为 Right;
- 设置 Vertical alignment 为 Bottom。
做完这些改变后,最后的 table row 看起来应该像这样:
列表在 Interface Builder 中开发完成,是时候填充一些数据了。
填充列表
首先创建一个 WKInterfaceController 的子类为列表提供数据。
在项目导航中右击 Watch Extension 组选择New File...。在弹出的对话框中选择 watchOS\Source\WatchKit Class 然后点击Next。命名新的类为 ScheduleInterfaceController,确保它是 WKInterfaceController 的子类并且语言设置为 Swift:
点击 Next,然后 Create。
当在代码编辑器中打开了新创建的文件,删除三个空的方法后就剩下重要的语句和类定义了。
重新打开 Watch\Interface.storyboard ,选择新的界面控制器。在 Identity Inspector,修改 Custom Class\Class 为 ScheduleInterfaceController:
在选中界面控制器的基础上,打开辅助编辑器确保它显示的是 ScheduleInterfaceController。然后按住 Ctrl从 Table 往 ScheduleInterfaceController 里面拖拽来创建一个 outlet:
命名 outlet 为 flightTable,确保类型设置为 WKInterfaceTable 然后点击 Connect。
现在你已经设置好自定义的类并且为 table 创建了一个 outlet,是时候填充一些数据了!
关闭辅助编辑器,打开 ScheduleInterfaceController.swift,在 outlet 的下面添加如下代码
var flights = Flight.allFlights()
这里你仅仅增加了一个 Flight 对象数组保存所有航班信息。
下一步,增加 awakeWithContext(_:) 的实现:
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
flightsTable.setNumberOfRows(flights.count, withRowType: "FlightRow")
}
这段代码通知 table 为 flights 数组中每个 flight 创建一个 Interface Builder 中的行实例。行的数量等于数组的大小,行的类型就是你在 storyboard 中设置的 identifier 的值。
编译运行。你会看到列表已经填充几行数据了:
你应该注意到列表中显示的是 Interface Builder 中的占位文本。为了修复这种情况,我们通过添加 row controller 分别配置每一行的 label。
添加 Row Controller
在项目导航中右击 Watch Extension 组然后选择 New File...。在出现的对话框中选择 watchOS\Source\WatchKit Class 然后点击 Next。命名新的类为 FlightRowContorller,确保这个类是 NSObject 的子类并且语言设置为 Swift 了:
点击 Next,之后 Create。
当新的文件在代码编辑器中打开了,在类的顶部增加如下代码:
@IBOutlet var separator: WKInterfaceSeparator!
@IBOutlet var originLabel: WKInterfaceLabel!
@IBOutlet var destinationLabel: WKInterfaceLabel!
@IBOutlet var flightNumberLabel: WKInterfaceLabel!
@IBOutlet var statusLabel: WKInterfaceLabel!
@IBOutlet var planeImage: WKInterfaceImage!
这里仅仅为每个你添加到 table row 中的 label 添加对应的 outlet。稍后会连接它们。
下一步,在这些 outlet 下面添加如下这个属性和对应的属性监视器:
// 1
var flight: Flight? {
// 2
didSet {
// 3
if let flight = flight {
// 4
originLabel.setText(flight.origin)
destinationLabel.setText(flight.destination)
flightNumberLabel.setText(flight.number)
// 5
if flight.onSchedule {
statusLabel.setText("On Time")
} else {
statusLabel.setText("Delayed")
statusLabel.setTextColor(UIColor.redColor())
}
}
}
}
下面一步步讲解是怎么回事:
- 你定义了类型为 Flight 的可选属性。记住,这个类是在 Flight.swift 中定义的,而 Flight.swift 是你在教程1中添加到 Watch Extension 中的共享代码的一部分;
- 添加了一个属性监视器,当属性被赋值的时候会触发;
- 当 flight 为 nil 的时候会提前退出。因为它是可选的,只有当 Flight 对象有效的时候才去设置标签的属性。
- 使用 flight 的相关属性去设置标签。
- 如果航班被延误了,改变标签的文本颜色,并相应地更新文本。
当 row controller 设置完成,现在需要更新 table row 来使用它。
打开 Watch\Interface.storyboard 然后在文档大纲中选择 FlightRow。使用 Identity Inspector,设置 Custom Class\Class 为 FlightRowController。
下一步,在文档大纲中右击FlightRow:
连接 planeImage 和 table row 中的 image,separator 与 table row 中的 separator。之后进行如下连线:
- destinationLabel: SFO
- flightNumberLabel: AA123
- originLabel: MAN
- statusLabel: On time
最后一步就是更新 ScheduleInterfaceController 来向列表中每个行控制器传递 Flight 对象。
打开 ScheduleInterfaceController.swift 之后在 awakeWithContext(_:) 底下添加如下代码:
for index in 0..<flightsTable.numberOfRows {
if let controller = flightsTable.rowControllerAtIndex(index) as? FlightRowController {
controller.flight = flights[index]
}
}
这里使用 for 循环遍历列表中每一行来访问指定索引的行控制器。假如你成功设置了 controller 的 flight 属性,会触发 didSet 监视器并且设置 table row 中所有的标签。
是时候看看劳动成果了。编译运行。你会看到 table row 已经被相关的航班信息填充了:
响应行的选中事件
首先去重写 WKInterfaceController 中主要负责处理 table row 选中事件方法定义。
往 ScheduleInterfaceController 中添加如下代码:
override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
let flight = flights[rowIndex]
presentControllerWithName("Flight", context: flight)
}
这里使用行的索引来检索出合适的航班信息。之后显示航班详情界面,作为 context 属性传递 flight 给这个界面。presentControllerWithName(_:context:) 方法的name参数是你在 storyboard 中设置identifier的值。
现在你需要更新 FlightInterfaceController 来使用 context 的值来设置它的界面。
打开 FlightInterfaceController.swift,找到 awakeWithContext(_:) 。替换这句代码:
flight = Flight.allFlights().first!
为如下代码:
if let flight = context as? Flight { self.flight = flight }
这里尝试转换 context 为 Flight 对象。如果转换成功就可以使用它来设置self.flight,这会触发属性监视器来设置界面。
最后,编译运行。如果你点击某个 table row ,你会看到航班详情界面模态弹出,显示你选中的航班的详情:
恭喜!你已经完成你的第一个列表并且使用真实数据填充它;太棒了!
总结
这里目前教程的完整实例项目。
在这片教程中你学习到如何往界面控制器中添加一个列表,设计 table row 界面,创建一个行控制器,处理 table row 选中,显示其他界面控制器,传递 contexts。在这20分钟里面做了很多事情!
如果您对本教程有任何疑问或意见,请参加下面的论坛进行讨论!