走进 WatchKit Framework

jopen 10年前

写在前面

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,如下图:

走进 WatchKit Framework

其中 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。

走进 WatchKit Framework

当Watch OS开始初始化我们Watch App的Storyboard中的UI时,iPhone端WatchKit Extension会生成对应的WKInterfaceController,并且响应initWithContext:方法。

当Watch OS显示当前加载的UI时,WatchKit Extension中对应的WKInterfaceController响应willActivate方法。

当用户切换页面或者停止使用时,WatchKit Extension中对应的WKInterfaceController响应didDeactivate方法。

走进 WatchKit Framework

从上图可知这三个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,它看起来像这样:

走进 WatchKit Framework

我们不但可以通过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在这个版本中并没有开放,期待苹果的下一次更新。