走进 WatchKit Framework
写在前面
WatchKit Apple提供的开发专题页面如下: https://developer.apple.com/watchkit/。 其中包含两个Demo,这两个Demo可以让大家快速的了解WatchKit的构建。
Watch App Architecture
每一个Apple Watch App和 iOS Extension一样仍然需要依赖一个主体App,Apple Watch App 包含两个部分:Watch App 和 WatchKit Extension,如下图:
其中 Watch App 部分位于用户的Apple Watch上,它目前为止只允许包含Storyboard文件和Resources文件。在我们的项目里,这一部分不包括任何代码。
WatchKit Extension 部分位于用户的iPhone安装的对应App上,这里包括我们需要实现的代码逻辑和其他资源文件。
这两个部分之间就是通过 WatchKit进行连接通讯。
WatchKit
WatchKit用来为开发者构建Apple Watch App。它所有的类如下,其中最上层的类继承于NSObject。
<pre><code>
WKInterfaceController
WKUserNotificationInterfaceController
WKInterfaceDevice
WKInterfaceObject
WKInterfaceButton
WKInterfaceDate
WKInterfaceGroup
WKInterfaceImage
WKInterfaceLabel
WKInterfaceMap
WKInterfaceSeparator
WKInterfaceSlider
WKInterfaceSwitch
WKInterfaceTable
WKInterfaceTimer
WKInterfaceController
</pre></code>
WKInterfaceController是我们开发Watch App的核心类,它的地位和之前使用的UIViewController一样。
每一个Watch App构建时,至少需要在Storyboard上设置一个WKInterfaceController实例作为程序入口。我们可以在Storyboard上使用Main Entry Point设置。
当用户launch了Watch App时,Watch OS 会开始加载程序中的Storyboard。我们在Storyboard中为每一个WKInterfaceController设置的响应事件,会在用户触发时在WatchKit Extension中响应。我们可以像以前一样push, pop, present 目标WKInterfaceController。
生命周期
WKInterfaceController一样也有自己的生命周期,以下几个API对应了几个不同的状态:
<pre><code>- (instancetype)initWithContext:(id)context;
- (void)willActivate;
- (void)didDeactivate;</pre></code>
当Watch OS加载App中的Storyboard时,iPhone端也会开始加载对应的WatchKit Extension。
当Watch OS开始初始化我们Watch App的Storyboard中的UI时,iPhone端WatchKit Extension会生成对应的WKInterfaceController,并且响应initWithContext:方法。
当Watch OS显示当前加载的UI时,WatchKit Extension中对应的WKInterfaceController响应willActivate方法。
当用户切换页面或者停止使用时,WatchKit Extension中对应的WKInterfaceController响应didDeactivate方法。
从上图可知这三个API,对应了Watch OS加载一个视图控制器的三个状态。我们在自己的WKInterfaceController类中,应该实现这三个API用来处理不同的情况:
- initWithContext: 我们可以在这里加载数据或者更新在StoryBoard中当前Controller添加的interface objects。
- willActivate 我们可以在这里更新interface objects或者处理其他事件
- didDeactivate 我们应该在这里清理task或者数据。在这里更新interface objects将会被系统忽略。
页面跳转
当用户和我们的APP进行交互时,有很多时候,我们需要进行页面的跳转。WKInterfaceController目前支持两组API进行页面跳转:
<pre><code>
- (void)pushControllerWithName:(NSString *)name context:(id)context
- (void)popController;
- (void)popToRootController;
- (void)presentControllerWithName:(NSString *)name context:(id)context;
- (void)presentControllerWithNames:(NSArray *)names contexts:(NSArray *)contexts;
- (void)dismissController;
- (void)becomeCurrentPage;
</pre></code>
Push,Pop, Present, Dismiss的行为和UIViewController中类似。我们可以在代码中,根据程序上下文的状态,控制跳转到某一个页面。
使用这一组API时有四点需要注意:
- Push和Present方法第一个参数是对应的在Storyboard中为WKInterfaceController设置的identifier字符串。WatchKit Extension
- 使用这几个API向Watch OS传递消息,真实的UI加载渲染行为是在Watch端进行。
- popToRootController是跳转到Watch App的Storyboard中Main Entry Point对应的Controller。
- presentControllerWithNames, 我们可以present一组Controller, 这一组Controller将以page control的形式展示。
- becomeCurrentPage 当页面是以page control的形式展现时,我们可以调用这个方法改变当前的page
另外一组API是:
<pre><code>- (id)contextForSegueWithIdentifier:(NSString *)segueIdentifier;
- (NSArray *)contextsForSegueWithIdentifier:(NSString *)segueIdentifier;
- (id)contextForSegueWithIdentifier:(NSString *)segueIdentifier inTable:(WKInterfaceTable *)table rowIndex:(NSInteger)rowIndex;
- (NSArray *)contextsForSegueWithIdentifier:(NSString *)segueIdentifier inTable:(WKInterfaceTable *)table rowIndex:(NSInteger)rowIndex;</pre></code>
当我们在应用设计的阶段就知道需要跳转的下一个WKInterfaceController时,我们可以直接在Storyboard中设置Triggered Segues。使用Segues时,Selection同样支持Push和Model两种跳转方式。
我们可以使用上面一组API进行跳转中的数据传递。
响应交互事件
WKInterfaceObject中像Button,Slider, Switch等控件可以和用户交互,我们和往常一样,可以在WKInterfaceController实现对应的Action,标记为IBAction,然后连接到Storyboard中。
这里特别的地方是,当我们的WKInterfaceController中包含WKInterfaceTable实例时,我们可以通过实现默认的- (void)table:(WKInterfaceTable *)table didSelectRowAtIndex:(NSInteger)rowIndex方法响应table中每一行的点击事件,这里和往常的UITableView的实现方式不太一样,更加简单。
Glance
Glance是 Watch App上新的概念,它主要作用是给用户一个短时的提醒。我们可以通过Storyboard创建一个Glance interface Controller.对应的WatchKit Extension中,它同样需要继承于WKInterfaceController,享有同样的生命周期。我们可以在其中实现自己的逻辑。
这里需要注意的是,Glance是可以和用户进行交互的。当用户Tap Glance页面时,会跳转到我们的Watch App中。这里可以在自定义的GlanceInterfaceController中使用- (void)updateUserActivity:(NSString *)type userInfo:(NSDictionary *)userInfo
传递数据。比如我们需要在用户点击Glance之后进入到某一个特定的页面,我们可以把目标页面的identifier和要传递的其他消息包装到字典中,然后在Initial Interface Controller中实现- (NSString *)actionForUserActivity:(NSDictionary *)userActivity context:(id *)context
方法跳转到目标页面,这里的userActivity就是上文传递的userInfo,返回的NSString是目标页面的identifier,context指针是目标页面initWithContext中context数据。
Notification && WKUserNotificationInterfaceController
当我们的主体App支持Notification时,Apple Watch将能够显示这些通知。Watch OS提供了默认的通知显示,当用户点击通知进入我们的App时,Initial Interface Controller中- (void)handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)remoteNotification
或者- (void)handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)localNotification
方法将会被响应,我们可以通过实现这两个方法获得通知的消息,跳转到目标页面。
我们同样可以通过Storyboard创建一个Notification interface Controller,这样可以实现自定义的通知界面。对应的WatchKit Extension中,它继承于WKUserNotificationInterfaceController,享有和WKInterfaceController同样的生命周期。我们可以通过实现下面两组API- (void)didReceiveRemoteNotification:(NSDictionary *)remoteNotification withCompletion:(void(^)(WKUserNotificationInterfaceType interface)) completionHandler
或者- (void)didReceiveLocalNotification:(UILocalNotification *)localNotification withCompletion:(void(^)(WKUserNotificationInterfaceType interface)) completionHandler
获得通知内容,并设置处理完成的回调Block。
Menu
我们可以通过Storyboard在界面中添加Menu,它看起来像这样:
我们不但可以通过Storyboard在Menu中添加Item,也可以通过WKInterfaceController中以下一组API,在上下文环境中添加相应的Item:
<pre><code>- (void)addMenuItemWithImage:(UIImage *)image title:(NSString *)title action:(SEL)action;
- (void)addMenuItemWithImageNamed:(NSString *)imageName title:(NSString *)title action:(SEL)action;
- (void)addMenuItemWithItemIcon:(WKMenuItemIcon)itemIcon title:(NSString *)title action:(SEL)action;
- (void)clearAllMenuItems;</pre></code>
WKInterfaceObject
WKInterfaceObject负责界面的元素,目前Apple公开了11个具体的子类用来展现各种不同类型的元素。它和之前的UIView或者UIView的子类不一样,WKInterfaceObject只负责在WatchKit Extension和Watch App中传递相应的事件,具体的UI渲染在Watch App中完成。
Watch App 采取的布局方式和 iOS App 完全不同。我们无法指定某个视图的具体坐标,也不能使用AutoLayout来进行布局。WatchKit只能在以“行”为基本单位进行布局。在一行中如果要显示多个元素,我们就要通过WKInterfaceGroup在行内进行列布局。
WKInterfaceTable
和学习iOS开发一样,先从一个TableView开始上手。目前在WatchKit中最复杂的界面元素也是WKInterfaceTable。
我们可以通过Storyboard直接在当前WKInterfaceController中添加一个Table,每一个Table默认包含一个Table Row Controller, 这个Table Row Controller作用相当于之前的Cell,不过这里是继承于NSObject。我们可以使用Table Row Controller中定义每一种Row的样式,然后设置一个唯一的identifier用来区分。
我们可以通过以下两组设置Table的每一行的样式,rowType对应Storyboard中Row Controller的identifier。
<pre><code>- (void)setRowTypes:(NSArray *)rowTypes;
- (void)setNumberOfRows:(NSInteger)numberOfRows withRowType:(NSString *)rowType;</pre></code>
我们可以通过- (id)rowControllerAtIndex:(NSInteger)index获得某一行对应的Row Controller。下面是一段在interface controller中初始化Table Rows的例子:
<pre><code>- (void)loadTableRows {
[self.interfaceTable setNumberOfRows:self.elementsList.count withRowType:@"default"];
// Create all of the table rows.
[self.elementsList enumerateObjectsUsingBlock:^(NSDictionary *rowData, NSUInteger idx, BOOL *stop) {
AAPLElementRowController *elementRow = [self.interfaceTable rowControllerAtIndex:idx];
[elementRow.elementLabel setText:rowData[@"label"]];
}];
}</pre></code>
我们同样可以使用下面的API进行添加,删除Table的Rows:
<pre><code>
- (void)insertRowsAtIndexes:(NSIndexSet *)rows withRowType:(NSString *)rowType;
- (void)removeRowsAtIndexes:(NSIndexSet *)rows;
WKInterfaceDevice
</pre></code>
这是一个单例类,可以获得当前Apple Watch的部分信息。目前公开的信息有:
<pre><code>
@property(nonatomic,readonly) CGRect screenBounds;
@property(nonatomic,readonly) CGFloat screenScale;
@property(nonatomic,readonly,strong) NSLocale *currentLocale;
@property(nonatomic,readonly,copy) NSString *preferredContentSizeCategory;
</pre></code>
另外我们可以使用这个类中的以下一组方法来缓存图片,以备将来继续使用:
<pre><code>
- (void)addCachedImage:(UIImage *)image name:(NSString *)name;
- (void)addCachedImageWithData:(NSData *)imageData name:(NSString *)name;
- (void)removeCachedImageWithName:(NSString *)name;
- (void)removeAllCachedImages;
已经缓存的图片,可以使用WKInterfaceImage中下面的API直接读取: - (void)setImageData:(NSData *)imageData;
- (void)setImageNamed:(NSString *)imageName;
WatchKit允许每一个App最多缓存20MB的图片,如果超过的话,WatchKit将从最老的数据开始删除,为新数据腾出空间。</pre></code>
总结
关于WatchKit Framework中API的知识点都基本包含在了上述笔记中。目前所提供的API功能有限,主要是信息的显示,通知的接收。更多关于多媒体或者传感器方面的API在这个版本中并没有开放,期待苹果的下一次更新。
转载自:http://chun.tips/blog/2014/11/19/zou-jin-watchkit-framework/