寫在前面
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在這個版本中並沒有開放,期待蘋果的下一次更新。