watchOS 2 教程(四): Watch Connectivity

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

下面一步步讲解以上代码怎么回事:

  1. 假如存在有效航班,没有登机牌,并且支持 Watch Connecivity,会继续进入发送消息的模块。在尝试与配对的手机做任何连接前你应该经常检查是否支持 Watch Connectivity。
  2. 设置 session 值为默认的 WCSession 单例。这会相应的触发属性观察器,在激活 session 之前 设置它的代理。
  3. 往配对的 iPhone app 发送消息。一个包括航班信息的字典被转发到 iPhone app,并且提供回调和错误处理。
  4. iPhone app 处理接收的消息然后返回数据给手表端。手表端从返回数据中提取登机牌的图片信息来创建一个 UIImage 对象。
  5. 如果操作成功了,设置 UIImage 为航班登机牌的图片,之后回到主线程调用 showBoardingPass() 来显示给用户。回调和错误处理是在后台线程中执行,所以如果你需要像现在这样更新界面,确保是在主线程中更新。
  6. 如果消息发送失败简单的打印错误到命令行。

这些是手表 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 的技巧。

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

推荐阅读更多精彩内容