走进 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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容