写在前面
-
WatchKit Apple提供的开发专题页面如下: https://developer.apple.com/watchkit/ 。其中包含两个Demo,这两个Demo可以让大家快速的了解WatchKit的构建。
-
@onevcat第一时间发布了博文: Apple WatchKit 初探 ,对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
。
WKInterfaceController
WKUserNotificationInterfaceController
WKInterfaceDevice
WKInterfaceObject
WKInterfaceButton
WKInterfaceDate
WKInterfaceGroup
WKInterfaceImage
WKInterfaceLabel
WKInterfaceMap
WKInterfaceSeparator
WKInterfaceSlider
WKInterfaceSwitch
WKInterfaceTable
WKInterfaceTimer
WKInterfaceController
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对应了几个不同的状态:
- (instancetype)initWithContext:(id)context;
- (void)willActivate;
- (void)didDeactivate;
当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进行页面跳转:
- (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;
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是:
- (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;
当我们在应用设计的阶段就知道需要跳转的下一个 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:
- (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;
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。
- (void)setRowTypes:(NSArray *)rowTypes;
- (void)setNumberOfRows:(NSInteger)numberOfRows withRowType:(NSString *)rowType;
我们可以通过 - (id)rowControllerAtIndex:(NSInteger)index
获得某一行对应的Row Controller。下面是一段在interface controller中初始化Table Rows的例子:
- (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"]];
}];
}
我们同样可以使用下面的API进行添加,删除Table的Rows:
- (void)insertRowsAtIndexes:(NSIndexSet *)rows withRowType:(NSString *)rowType;
- (void)removeRowsAtIndexes:(NSIndexSet *)rows;
WKInterfaceDevice
这是一个单例类,可以获得当前Apple Watch的部分信息。目前公开的信息有:
@property(nonatomic,readonly) CGRect screenBounds;
@property(nonatomic,readonly) CGFloat screenScale;
@property(nonatomic,readonly,strong) NSLocale *currentLocale;
@property(nonatomic,readonly,copy) NSString *preferredContentSizeCategory;
另外我们可以使用这个类中的以下一组方法来缓存图片,以备将来继续使用:
- (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将从最老的数据开始删除,为新数据腾出空间。
总结
关于WatchKit Framework中API的知识点都基本包含在了上述笔记中。目前所提供的API功能有限,主要是信息的显示,通知的接收。更多关于多媒体或者传感器方面的API在这个版本中并没有开放,期待苹果的下一次更新。