watchOS 2 教程(二):列表

原文: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())
      }
    }
  }
}

下面一步步讲解是怎么回事:

  1. 你定义了类型为 Flight 的可选属性。记住,这个类是在 Flight.swift 中定义的,而 Flight.swift 是你在教程1中添加到 Watch Extension 中的共享代码的一部分;
  2. 添加了一个属性监视器,当属性被赋值的时候会触发;
  3. 当 flight 为 nil 的时候会提前退出。因为它是可选的,只有当 Flight 对象有效的时候才去设置标签的属性。
  4. 使用 flight 的相关属性去设置标签。
  5. 如果航班被延误了,改变标签的文本颜色,并相应地更新文本。

当 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分钟里面做了很多事情!

如果您对本教程有任何疑问或意见,请参加下面的论坛进行讨论!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,719评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,337评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,887评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,488评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,313评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,284评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,672评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,346评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,644评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,700评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,457评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,316评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,706评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,261评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,648评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,859评论 2 335

推荐阅读更多精彩内容