走进 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
。
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在这个版本中并没有开放,期待苹果的下一次更新。
- 本文是关于WatchKit Framework的学习笔记。有不对的地方,欢迎大家指正。
- 本文对应参考文献为 WatchKit Framework Reference from Apple Delevoper
- 本文所有图片来源于Apple Delevoper
- 本文由@Chun发表于Chun Tips .
- 版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0