2014/11/20 | 分类: IOS, 开发 | 0 条评论 | 标签: APPLE, APPLE WATCHios
分享到:1swift
原文出处: 王巍的博客 (@onevcat) 欢迎分享原创到伯乐头条框架
随着今天凌晨 Apple 发布了初版的 Watch Kit 的 API,对于开发者来讲,这款新设备的一些更详细的信息也算是逐渐浮出水面。能够说初版的 WatchKit 开放的功能整体仍是使人满意的。Apple 在承诺逐渐开放的方向上继续前进。原本在 WWDC 以后预期 Today Widget 会是各种新颖 app 的舞台以及对 iOS 功能的极大扩展,可是随着像 Launcher 和 PCalc 这些创意型的 Today Widget 接连被下架事件,让开发者也不得不下调对 WatchKit 的预期。可是至少从如今的资料来看,WatchKit 是容许进行复杂交互以及完成一些独立功能的。虽然须要依托于 iPhone app,可是至少可以发挥的舞台和空间要比我原先想象的大很多。less
固然,由于设备自己的不管是电量仍是运算能力的限制,在进行 Watch app 开发的时候也仍是有不少掣肘。如今 Watch app 仅仅只是做为视图显示和回传用户交互的存在,可是考虑到这是这款设备的初版 SDK,另外 Apple 也有承诺以后会容许真正运行在 Watch 上的 app 的出现,Apple Watch 和 WatchKit 的将来仍是很值得期待的。ide
废话再也不多,咱们来简单看看 WatchKit 的一些基本信息吧。oop
首先须要明确的是,在 iOS 系统上,app 本体是核心。全部的运行实体都是依托在本体上的:在 iOS 8 以前这是毋庸置疑的,而在 iOS 8 中添加的各类 Extension 也必须随¥同 app 本体捆绑,做为 app 的功能的补充。Watch app 虽然也相似于此,咱们要针对 Apple Watch 进行开发,首先仍是须要创建一个传统的 iOS app,而后在其中添加 Watch App 的 target。在添加以后,会发现项目中多出两个 target:其中一个是 WatchKit 的扩展,另外一个是 Watch App。在项目相应的 group 下能够看到,WatchKit Extension 中含有代码 (InterfaceController.h/m
等),而 Watch App 里只包含了 Interface.storyboard
。不过暂时看来的好消息是 Apple 并无像对 iPhone Extension 那样明确要求针对 Watch 开发的 app 必须仍是以 iOS app 为核心。也就是说,将 iOS app 空壳化而专一提供 Watch 的 UI 和体验也许是被容许的。
在应用安装时,负责逻辑部分的 WatchKit Extension 将随 iOS app 的主 target 被一同安装到 iPhone 中,而负责界面部分的 WatchKit App 将会在主程序安装后由 iPhone 检测有没有配对的 Apple Watch,并提示安装到 Apple Watch 中。因此在实际使用时,全部的运算、逻辑以及控制实际上都是在 iPhone 中完成的。在须要界面刷新时,由 iPhone 向 Watch 发送指令进行描画并在手表盘面上显示。反过来,用户触摸手表交互时的信息也由手表传回给 iPhone 并进行处理。而这个过程 WatchKit 会在幕后为咱们完成,并不须要开发者操心。咱们须要知道的就是,原则上来讲,咱们应该将界面相关的内容放在 Watch App 的 target 中,而将全部代码逻辑等放到 Extension 里。
在手表上点击 app 图标运行 Watch App 时,手表将会负责唤醒手机上的 WatchKit Extension。而 WatchKit Extension 和 iOS app 之间的数据交互需求则由 App Groups 来完成,这和 Today Widget 以及其余一些 Extension 是同样的。若是你尚未了解过相关内容,能够参看我以前写过的一篇 Today Extension 的教程。
WKInterfaceController 和生命周期
WKInterfaceController
是 WatchKit 中的 UIViewController
同样的存在,也会是开发 Watch App 时花时间最多的类。每一个 WKInterfaceController
或者其子类应该对应手表上的一个整屏内容。可是须要记住整个 WatchKit 是独立于 UIKit 而存在的,WKInterfaceController
是一个直接继承自 NSObject
的类,并无像 UIKit
中UIResponser
那样的对用户交互的响应功能和完备的回调。
不只在功能上相对 UIViewController
简单不少,在生命周期上也进行了大幅简化。每一个WKInterfaceController
对象必然会被调用的生命周期方法有三个,分别是该对象被初始化时的 -initWithContext:
,将要呈现时的 -willActivate
以及呈现结束后的 -didDeactivate
。一样类比 UIViewController
的话,能够将它们理解为分别对应 -viewDidLoad
,viewWillAppear:
以及 -viewDidDisappear:
。虽然看方法名和实际使用上可能你会认为 -initWithContext:
应该对应 UIViewController
的 init
或者initWithCoder:
这样的方法,可是事实上在 -initWithContext:
时WKInterfaceController
中的“视图元素” (请注意这里我加上了引号,由于它们不是真正的视图,稍后会再说明) 都已经初始化完毕可用,这其实和 -viewDidLoad
中的行为更加类似。
咱们通常在 -initWithContext:
和 -willActivate
中配置“视图元素”的属性,在 -didDeactivate
中停用像是 NSTimer
之类的会 hold 住 self
的对象。须要特别注意的是,在 -didDeactivate
中对“视图元素”属性进行设置是无效的,由于当前的WKInterfaceController
已经非活跃。
WKInterfaceObject 及其子类
WKInterfaceObject
负责具体的界面元素设置,包括像是WKInterfaceButton
,WKInterfaceLabel
或 WKInterfaceImage
这类物件,也就是咱们上面所提到的“视图元素”。可能一开始会产生错觉,以为 WKInterfaceObject
应该对应UIView
,但其实上并不是如此。WKInterfaceObject
只是 WatchKit 的实际的 View 的一个在 Watch Extension 端的代理,而非 View 自己。Watch App 中实际展示和渲染在屏幕上的 view 对于代码来讲是非直接可见的,咱们只能在 Extension target 中经过对应的代理对象对属性进行设置,而后在每一个 run loop 须要刷新 UI 时由 WatchKit 将新的属性值从手机中传递给手表中的 Watch App 并进行界面刷新。
反过来,手表中的实际的 view 想要将用户交互事件传递给 iPhone 也须要经过WKInterfaceObject
代理进行。每一个可交互的 WKInterfaceObject
子类都对应了一个 action,好比 button 对应点击事件,switch 对应开或者关的状态,slider 对应一个浮点数值代表选取值等等。关联这些事件也很简单,直接从 StoryBoard 文件中 Ctrl 拖拽到实现中就能生成对应的事件了。虽然 UI 资源文件和代码实现是在不一样的 target 中的,可是在 Xcode 中的协做已然完美无缺。
Watch App 采起的布局方式和 iOS app 彻底不一样。你没法自由指定某个视图的具体坐标,固然也不能使用像 AutoLayout 或者 Size Classes 这样的灵活的界面布局方案。WatchKit 提供的布局可能性和灵活性相对较小,你只能在以“行”为基本单位的同时经过 group 来在行内进行“列”布局。这带来了相对简单的布局实现,固然,同时也是对界面交互的设计的一种挑战。
另外值得一提的是,随着 WatchKit 的出现及其开发方式的转变,代码写 UI 仍是使用 StoryBoard 这个争论了多年的话题能够暂时落下帷幕了。针对 Watch 的开发不能使用代码的方式。首先,全部的 WKInterfaceObject
对象都必需要设计的时候经由 StoryBoard 进行添加,运行时咱们没法再向界面上添加或者移除元素 (若是有移除须要的,可使用隐藏);其次WKInterfaceObject
与布局相关的某些属性,好比行高行数等,不可以在运行时进行变动和设定。基原本说在运行时咱们只可以改变视图的内容,以及经过隐藏某些视图元素来达到有限地改变布局 (其余视图元素会试图填充被隐藏的元素)。
总之,代码进行 UI 编写的传统,在 Apple 的不断努力下,于 WatchKit 发布的今天,被正式宣判了死刑。
Table 和 Context Menu
大部分 WKInterfaceObject
子类都很直接简单,可是有两个我想要单独说一说,那就是WKInterfaceTable
和 WKInterfaceMenu
。UITableView
你们都很熟悉了,在 WatchKit 中的 WKInterfaceTable
虽然也是用来展现一组数据,可是由于 WatchKit API 的数据传输的特色,使用上相较 UITableView
有很大不一样和简化。首先不存在 DataSource 和 Delegate,WKInterfaceTable
中须要呈现的数据数量直接由其实例方法 -setNumberOfRows:withRowType:
进行设定。在进行设定后,使用 -rowControllerAtIndex:
枚举全部的 rowController
进行设定。这里的 rowController
是在 StoryBoard 中所设定的至关于 UITableViewCell
的东西,只不过和其余 WKInterfaceObject
同样,它是直接继承自NSObject
的。你能够经过自定义 rowController
并链接 StoryBoard 的元素,并在取得rowController
对其进行设定,便可完成 table 的显示。代码大概是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// MyRowController.swift
import Foundation
import WatchKit
class MyRowController: NSObject {
@IBOutlet weak var label: WKInterfaceLabel!
}
// InterfaceController.swift
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
@IBOutlet weak var table: WKInterfaceTable!
let data = ["Index 0","Index 1","Index 2"]
override init(context: AnyObject?) {
// Initialize variables here.
super.init(context: context)
// Configure interface objects here.
NSLog("%@ init", self)
// 注意须要在 StoryBoard 中设置 myRowControllerType
// 相似 cell 的 reuse id
table.setNumberOfRows(data.count, withRowType: "myRowControllerType")
for (i, value) in enumerate(data) {
if let rowController = table.rowControllerAtIndex(i) as? MyRowController {
rowController.label.setText(value)
}
}
}
}
|
对于点击事件,并无一个实际的 delegate 存在,而是相似于其余 WKInterfaceObject
那样经过 action 将点击了哪一个 row 做为参数发送回 WKInterfaceController
进行处理。
另外一个比较好玩的是 Context Menu,这是 WatchKit 独有的交互,在 iOS 中并不存在。在任意一个 WKInterfaceController
界面中,长按手表屏幕,若是当前 WKInterfaceController
中存在上下文菜单的话,就会尝试呼出找这个界面对应的 Context Menu。这个菜单最多能够提供四个按钮,用来针对当前环境向用户征询操做。由于手表屏幕有限,在信息显示的同时再放一些交互按钮是挺不现实的一件事情,也会很丑。而上下文菜单很好地解决了这个问题,相信长按呼出交互菜单这个操做会成为从此 Watch App 的一个很标准的交互操做。
添加 Context Menu 很是简单,在 StoryBoard 里向 WKInterfaceController
中添加一个 Menu,并在这个 Menu 里添加对应的 MenuItem 就好了。在 WKInterfaceController
咱们也有对应的 API 来在运行时根据上下文环境进行 MenuItem 的添加 (这是少数几个容许咱们在运行时添加元素的方法之一)。
1
2
3
4
|
-addMenuItemWithItemIcon:title:action:
-addMenuItemWithImageNamed:title:action:
-addMenuItemWithImage:title:action:
-clearAllMenuItems
|
可是 Menu 和 MenuItem 对应的类 WKInterfaceMenu
和 WKInterfaceMenuItem
咱们是没有办法拿到的。没错,它们甚至都没有存在于文档里 :(
WKInterfaceController
的内建的导航关系基本上分为三类。首先是像UINavigationController
控制的相似栈的导航方式。相关的 API 有 -pushControllerWithName:context:
,-popController
以及 -popToRootController
。后两个我想没必要太多解释,对于第一个方法,咱们须要使用目标 controller 的 Identifier
字符串 (没有你只能在 StoryBoard 里进行设置) 进行建立。context
参数也会被传递到目标 controller 的 -initWithContext:
中,因此你能够以此来在 controller 中进行数据传递。
另外一种是咱们你们熟悉的 modal 形式,对应 API 是 -presentControllerWithName:context:
和 -dismissController
。对于这种导航,和 UIKit
中的不一样之处就是在目标 controller 中会默认在左上角加上一个 Cancel 按钮,点击的话会直接关闭被 present 的 controller。我只想说 Apple 终于想通了,每一个 modal 出来的 controller 都是须要关闭的这个事实…
最后一种导航方式是相似 UIPageController
的分页式导航。在 iOS app 中,在应用第一次开始时的教学模块中这种导航方式很是常见,而在 WatchKit 里能够说获得了发扬光大。事实上我我的也认为这会是 WatchKit 里最符合使用习惯的导航方式。在 WatchKit 上的 page 导航可能会和 iOS app 的 Tab 导航所提供的功能相对应。
在实现上,page 导航须要在 StoryBoard 中用 segue 的方式将不一样 page 进行链接,新添加的 next page
segue 就是干这个的:
另外 modal 导航的另外一个 API -presentControllerWithNames:contexts:
接受复数个的names
和 context
,经过这种方式 modal 呼出的复数个 Controller 也将以 page 导航方式呈现。
固然,做为 StoryBoard 的经典使用方式,modal 和 push 的导航方式也是能够在 StoryBoard 中经过 segue 来实现的。同时 WatchKit 也为 segue 的方式提供了必要的 API。
由于整个架构和 UIKit
彻底不一样,因此不少以前的实践是没法直接搬到 WatchKit App 中的。
图像处理
在 UIKit
中咱们显示图片通常使用 UIImageView
,而后为其 image
属性设置一个建立好的UIImage
对象。而对于 WatchKit 来讲,最佳实践是将图片存放在 Watch App 的 target 中 (也就是 StoryBoard 的那个 target),在对 WKInterfaceImage
进行图像设置时,尽可能使用它的 -setImageNamed:
方法。这个方法将只会把图像名字经过手机传递到手表,而后由手表在本身的 bundle 中寻找图片并加载,是最快的途径。注意咱们的代码是运行在于手表的 Watch App 不一样的设备上的,虽然咱们也能够先经过 UIImage
的相关方法生成 UIImage
对象,而后再用 -setImage:
或者 -setImageData:
来设置手表上的图片,可是这样的话咱们就须要将图片放到 Extension 的 target 中,而且须要将图片的数据经过蓝牙传到手表,通常来讲这会形成不可忽视的延迟,会很影响体验。
若是对于某些状况下,咱们只能在 Extension 的 target 中得到图片 (好比从网络下载或者代码动态生成等),而且须要重复使用的话,最好用 WKInterfaceDevice
的 -addCachedImage:name:
方法将其缓存到手表中。这样,当咱们以后再使用这张图片的时候就能够直接经过 -setImageNamed:
来快速地从手表上生成并使用了。每一个 app 的 cache 的尺寸大约是 20M,超过的话 WatchKit 将会从最老的数据开始删除,以腾出空间存储新的数据。
动画
由于没法拿到实际的视图元素,只有 WKInterfaceObject
这样的代理对象,以及布局系统的限制,因此复杂的动画,尤为是 UIView
系列或者是 CALayer
系列的动画是没法实现的。如今看来惟一可行的是帧动画,经过为 WKInterfaceImage
设置包含多个 image 的图像,或者是经过计时器定时替换图像的话,能够实现帧动画。虽然 Apple 本身的例子也经过这种方法实现了动画,可是对于设备的存储空间和能耗均可能会是挑战,还须要实际拿到设备之后进行实验和观察。
其余 Cocoa Touch 框架的使用
Apple 建议最好不要使用那些须要 prompt 用户许可的特性,好比 CoreLocation 定位等。由于实际的代码是在手机上运行的,这类许可也会在手机上弹出,可是用户并不必定正好在看手机,因此极可能形成体验降低。另外大部分后台运行权限也是不建议的。
对于要获取这些数据和权限,Apple 建议仍是在 iOS app 中完成,并经过 App Groups 进行数据共享,从而在 Watch Extension 中拿到这些数据。
代码分享
由于如今一个项目会有不少不一样的 target,因此使用 framework 的方式封装不一样 target 的公用部分的代码,而只在各 target 中实现界面相关的代码应该是必行的了。这么作的优势不只是能够减小代码重复,也会使代码测试和品质获得提高。若是尚未进行逻辑部分的框架化和测试分离的话,在实现像各种 Extension 或者 Watch App 时可能会遇到很是多的麻烦。
由于若是原有 app 有计划进行扩展推出各类 Extension 的话,将逻辑代码抽离并封装为 framework 应该是优先级最高的工做。另外新开的项目若是没有特殊缘由,也强烈建议使用 framework 来组织通用代码。
除了 Watch App 本体之外,Glance 和手表的 Notification 也是重要的使用情景。Notification 虽然概念上比较简单,可是相对于 iOS 的通知来讲是天差地别。WatchKit 的通知容许开发者自行构建界面,咱们能够经过 payload 设置比较复杂和带有更多信息的通知,包括图像,大段文字甚至能够交互的按钮,而不是像 iOS 上那样被限制在文字和一个对话框内。首先不管是经过 Local 仍是 Remote 进行的通知发送会先到达 iPhone,而后再由 iPhone 根据内容判断是否转发到手表。WatchKit App 接收到通知后先会显示一个简短的通知,告诉用户这个 app 有一个通知。若是用户对通知的内容感兴趣的话,能够点击或者抬手观看,这样由开发者自定义的长版本的通知就会显现。
Glance 是 WatchKit 的新概念,它容许 Watch App 展现一个布局固定的WKInterfaceController
页面。它和 Watch App 本体相对地位至关于 iOS 上的 Today Widget 和 iOS app 自己的地位,是做为手表上的 app 的最重要的信息展现出现的。Glance 正如其名,是短时存在的提醒,不能存在可交互的元素。不过若是用户点击 Glance 页面的话,是能够启动到 Watch App 的。如今关于 Glance 自己如何启动和呈现还不是很明确,猜想是某种相似 Today Widget 的呈现方式?(好比按下两次表侧面的旋钮)
WatchKit 整体使人满意,提供的 API 和开发环境已经足够开发者做出一些有趣的东西。可是有几个如今看来很明显的限制和但愿能增强的方向。
首先是从如今来看 WatchKit 并无提供任何获取设备传感信息的 API。不管是心跳、计步或者是用户是否正在佩戴 Watch 的信息咱们都是拿不到的,这限制了不少数据收集和监视的健康类 app 的制做。若是但愿请求数据,仍是不得不转向使用 HealthKit。可是随着 iPhone 6 和 6s 的大屏化,在运动时携带 iPhone 的人能够说是变少了。若是 Watch 不能在没有 iPhone 配对的状况下收集记录,并在以后和 iPhone 链接后将数据回传的话,那 Apple 的健康牌就失败了一大半。相信 Apple 不会放过这种把用户捆绑的机会…不过若是第三方应用能实时获取用户的佩戴情况的话,相信会有不少有意思的应用出现。
另外做为在发布会上鼓吹的交互革命的旋钮和触感屏幕,如今看来并无开听任何 API 供开发者使用,因此咱们没法得知用户旋转了手表旋钮这个重要的交互事件。如今看来咱们能获取的操做仅只是用户点击屏幕上的按钮或者拖动滑条这个层级,从这个角度来讲,如今的 WatchKit 还远没达到能够颠覆移动应用的地步。
但愿以后 Apple 会给咱们带来其余的好消息吧。
总之,舞台已经搭好,以后唱什么戏,就要看咱们的了。