原文:watchOS 2 Tutorial Part 3: Animation
欢迎回到 watchOS 2 系列教程!
开始
打开 Watch\Interface.storyboard,从对象库拖动一个 Interface Controller 到 storyboard 画板中。选中控制器,打开属性检查器做如下修改:
- 设置 Identifier 为 BoardingPass;
- 设置 Insets 为 Custom;
- 设置 Top inset 为 6。
因为这个界面非常像 check-in 界面,设计界面有时候有些重复的工作,这时候你要灵活一点。
在文档大纲中点开 CheckIn Scene,选择那个包括起点和终点标签的组,之后 Edit\Copy:
点击 storyboard 中那个新的控制器的任何地方,选择 Edit\Paste。这个只在直接往控制器里面粘贴时候有用,而往文档大纲中粘贴没有用,但是我也不知道为什么。
新的控制器应该是这样:
下一步,从对象库拖动一个 Image 放到新的控制器中,确保它与你刚才粘贴的组同级,而不是子节点:
image 控件有两个目的;最初它显示动画图片序列来告诉用户发生什么事情,之后当手表从手机获取到登机牌,image 会显示它。
下载压缩文件,解压缩文件,然后拖动文件夹到 Watch\Assets.xcassets 目录中。
确保拖动的是文件夹而不是其中的文件。这会在 asset catalog 中创建一个新的叫 Activity 的组,它包含一些图片集合:
当你正在从配对的手机中请求登机牌的时候,用这个图片序列显示不确定进度指示器。
重新打开 Watch\Interface.storyboard 然后选择之前那个 image。使用你的老朋友属性检查器做如下修改:
- 设置 Image 为 Activity。自动补全有可能会建议例如 Activity1,所以确保你输入的是 Activity;
- 设置 Animate 为 Yes;
- 设置 Duration 为1;
- 选中 Animate on Load;
- 设置 Horizontal alignment 为 Center;
- 设置 Vertical alignment 为 Center;
- 设置 Width 为 Fixed,值为66;
- 设置 Height 为 Fixed,值为66;
当修改完成,你的属性检查器应该像这样:
控制器应该像这样:
可以看到 Image 的预览图片是一个又大又模糊的问题标记,不要担心;因为没有叫 Activity 的图片,所以 IB 不能实时预览动画图片-但是请相信我,运行时就没这问题了。
设计完登机牌界面。现在创建 WKInterfaceController 的子类来做后续的工作。
创建控制器
在项目导航中右击 Watch Extension 组,选择 New File...。当对话框弹出来后选择 watchOS\Source\WatchKit Class 然后点击 Next。命名新的类为 BoardingPassInterfaceController,确保它是 WKInterfaceController 的子类并且语言设置为 Swift:
点击 Next,之后 Create。
当新的文件在代码编辑器中打开了,删除三个空的方法,只剩下重要代码和类定义。
之后,在类的顶部添加如下 outlets:
@IBOutlet var originLabel: WKInterfaceLabel!
@IBOutlet var destinationLabel: WKInterfaceLabel!
@IBOutlet var boardingPassImage: WKInterfaceImage!
这里仅仅为刚才创建的图片控件和两个标签增加连线。只要一瞬间你就能连接他们。
在连线下面增加如下代码:
var flight: Flight? {
didSet {
if let flight = flight {
originLabel.setText(flight.origin)
destinationLabel.setText(flight.destination)
}
}
}
这又是我们的老朋友 flight 和它的属性观察器! 虽然你知道即将发生什么,但是让我们来回顾一下,你添加了一个可选的 Flight 类型属性,包括一个属性观察器。当观察器触发,尝试解包 flight,当解包成功使用 flight 来配置两个标签。
现在仅仅需要在控制器第一次打开的时候设置 flight 属性。添加如下代码到 BoardingPassInterfaceController:
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
if let flight = context as? Flight { self.flight = flight }
}
另一个老朋友;尝试解包转换 context 为 Flight 对象!如果转换成功使用它来设置 self.flight,相应的触发属性观察器来配置界面。
我保证这就是练习的样板代码。:]
现在,打开 Watch\Interface.storyboard 选择登机牌控制器。在 Identity Inspector 中,修改 Custom Class\Class 为 BoardingPassInterfaceController:
在文档大纲中右击 BoardingPass 打开 outlets 和 actions 弹出框。连接 boardingPassImage 到 image:
最后,连接 destinationLabel 到 文本为 SFO 的标签,连接 originLabel 到文字是 MAN 的标签。
当完成这些操作,是时候更新 ScheduleInterfaceController 代码, 一旦用户登记了就打开登机牌界面。
打开登机牌界面
打开 ScheduleInterfaceController.swift 找到 table(_:didSelectRowAtIndex:)。替换这句代码:
let controllers = ["Flight", "CheckIn"]
为下面这句代码:
let controllers = flight.checkedIn ? ["Flight", "BoardingPass"] : ["Flight", "CheckIn"]
这里仅仅判断用户是否登记过选中的航班,如果登记过就显示航班详情和登机牌界面。如果没有,代替显示航班详情和登记界面。
编译运行。点击第一个航班,往左清扫,点击 Check In。再次点击相同的航班,往左清扫,你会看到登机牌界面,显示不确定进度指示器:
是时候深入学习新的 Watch Connectivity 框架并且使用它请求真实的登机牌数据。
请求登机牌
打开 BoardingPassInterfaceController.swift 导入 Watch Connectivity 框架:
import WatchConnecivity
下一步,在上面定义的 flight 的下面添加如下属性:
var session: WCSession? {
didSet {
if let session = session {
session.delegate = self
session.activateSession()
}
}
}
这里添加一个新的类型为 WCSession 的可选属性。在手表和手机两个设备间的所有连接操作都是由它处理的;你自己并不需要实例化这个类,而是使用框架提供的单例。你已经添加属性观察器了,当它触发了,尝试解包 session。当解包成功设置 session 的代理,之后激活它。
即使你不实现类的任何代理方法,你任然需要在激活前设置 session 的代理,不然情况会变得未知。
Xcode 可能会警告 BoardingPassInterfaceController 没有遵循 WCSessionDelegate 协议,所以在 BoardingPassInterfaceController.swift 的底部添加如下空的扩展:
extension BoardingPassInterfaceController: WCSessionDelegate {
}
下一步,往 BoardingPassInterfaceController 添加如下帮助方法:
private func showBoardingPass() {
boardingPassImage.stopAnimating()
boardingPassImage.setWidth(120)
boardingPassImage.setHeight(120)
boardingPassImage.setImage(flight?.boardingPass)
}
它会在两处调用 - 如果航班已经有登机牌了在 flight 的属性观察器中调用,还有另外一处是你发给你的 iPhone 的消息回调。实现非常简单-停止图片动画,增加图片大小,之后设置显示到登机牌的图片。
首先更新属性观察器,往 flight 属性观察器中的 if 代码块的底部增加如下代码
if let _ = flight.boardingPass {
showBoardingPass()
}
只有当 flight 存在一个登机牌的时候调用 showBoardingPass() 方法。
最后一部分代码是往 iPhone 发送请求。在 awakeWithContext(_:) 代码的下面添加如下代码:
override func didAppear() {
super.didAppear()
// 1
if let flight = flight where flight.boardingPass == nil && WCSession.isSupported() {
// 2
session = WCSession.defaultSession()
// 3
session!.sendMessage(["reference": flight.reference], replyHandler: { (response) -> Void in
// 4
if let boardingPassData = response["boardingPassData"] as? NSData, boardingPass = UIImage(data: boardingPassData) {
// 5
flight.boardingPass = boardingPass
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.showBoardingPass()
})
}
}, errorHandler: { (error) -> Void in
// 6
print(error)
})
}
}
下面一步步讲解以上代码怎么回事:
- 假如存在有效航班,没有登机牌,并且支持 Watch Connecivity,会继续进入发送消息的模块。在尝试与配对的手机做任何连接前你应该经常检查是否支持 Watch Connectivity。
- 设置 session 值为默认的 WCSession 单例。这会相应的触发属性观察器,在激活 session 之前 设置它的代理。
- 往配对的 iPhone app 发送消息。一个包括航班信息的字典被转发到 iPhone app,并且提供回调和错误处理。
- iPhone app 处理接收的消息然后返回数据给手表端。手表端从返回数据中提取登机牌的图片信息来创建一个 UIImage 对象。
- 如果操作成功了,设置 UIImage 为航班登机牌的图片,之后回到主线程调用 showBoardingPass() 来显示给用户。回调和错误处理是在后台线程中执行,所以如果你需要像现在这样更新界面,确保是在主线程中更新。
- 如果消息发送失败简单的打印错误到命令行。
这些是手表 app 端处理。现在需要相应的更新 iPhone app 端了。
回应请求
首先,导入 Watch Connectivity 框架:
import WatchConnectivity
之后,在 window 下面添加如下代码:
var session: WCSession? {
didSet {
if let session = session {
session.delegate = self
session.activateSession()
}
}
}
操作与 BoardingInterfaceController 中命名一样。简单的一个类型 WCSession 的可选属性,包括属性观察器,当触发了观察器,尝试解包 session。如果解包成功设置 session 的代理并且激活它。
下一步,在 AppDelegate.swift 文件中添加如下扩展:
extension AppDelegate: WCSessionDelegate {
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
if let reference = message["reference"] as? String, boardingPass = QRCode(reference) {
replyHandler(["boardingPassData": boardingPass.PNGData])
}
}
}
这里实现了 WCSessionDelegate 方法负责接收消息。从手表传过来的字典中提取航班信息,之后用 Alexander Schuch 写的牛逼的 QRCode 库来生成二维码,如果生成成功,调用回调函数,传递图片信息到手表 app。
最后,设置 session。添加如下代码到application(_:didFinishLaunchingWithOptions:) 方法中:
if WCSession.isSupported() {
session = WCSession.defaultSession()
}
这里确保支持 Watch Connectivity ,之后设置 session 为框架提供的默认的 WCSession 单例。
你现在能够与 iPhone app 进行双向对话了。
编译运行。按照如上步骤来登记航班然后查看登机牌。这次登机牌应该等段时间之后才能出现:
祝贺!你已经完成了使用 Watch Connectivity 向 iPhone app 请求登机牌;棒极了。
下一步做什么?
这是系列教程的完整示例项目
在本教程中,你学习如何在 watch app 和配对的 iPhone app 间发送实时消息,如何在两个设备间传递图片信息。
如果你喜欢这个系列想要更多的学习关于 watchOS 2 开发知识,来看看我们的 watchOS 2 by Tutorials 书,它会教你很多开发 watchOS 2 apps 的技巧。