项目作者: WildDogTeam

项目描述 :
lib-ios-watchkit 是基于 Wilddog SDK 在 WatchKit 上使用的教程。
高级语言: Objective-C
项目地址: git://github.com/WildDogTeam/lib-ios-watchkit.git
创建时间: 2015-10-15T07:20:47Z
项目社区:https://github.com/WildDogTeam/lib-ios-watchkit

开源协议:MIT License

下载


用Wilddog开发WatchKit

我们很容易用 Wilddog 开发 WatchKit app。这个指南会提供 WatchKit 的基本概述和引导你如何将 Wilddog 应用到你的 WatchKit app 的过程。

创建一个新的 Watch App

打开 Xcode,建立一个新工程。选择 iOS > Application > Single View Application。工程建立完成后,点击菜单栏 File > New > Target > Apple Watch。在 Apple Watch 栏中选中 WatchKit App,点击 Next > Finish > Activate 完成新建工程操作。

创建一个 WatchKit App Extension

Watch App 工程

Watch App 主要有三个部分:Host app, WatchKit Extension 和 WatchApp。

Host app

Host app 是该项目的主要的应用程序。这是用户用 iOS 设备启动应用程序。

WatchKit Extension

WatchKit Extension 是一个在运行 iOS 设备的应用程序扩展。截至 watchOS 1.0,Watch App 的代码运行在 iOS 设备上,而不是 Apple Watch 上。

WatchApp

WatchApp 工程包含 Watch App 的 interface。这是 Watch App 运行在 Apple Watch 的唯一部分。

创建 Interface

让我们建立一个简单的界面。打开在 WatchKit app 下的Interface.storyboard。在Interface.storyboard中添加一个 label 和 button。

在 WatchKit Extension 下的InterfaceController类中,添加 outlet 和 action。如:

Objective-C

  1. @interface InterfaceController()
  2. @property (weak, nonatomic) IBOutlet WKInterfaceLabel *labelUpdate;
  3. @end
  4. @implementation InterfaceController
  5. - (IBAction)updateButtonDidTouch {
  6. }
  7. @end

Swift

  1. @IBOutlet weak var awesomeLabel: WKInterfaceLabel!
  2. @IBAction func updateButtonIsTouched() {
  3. }

创建一个 Wilddog 引用

在 WatchKit Extension 中,打开InterfaceController,加入 Wilddog 引用。

Objective-C

  1. #import "InterfaceController.h"
  2. #import <Wilddog/Wilddog.h>
  3. @interface InterfaceController()
  4. // reference property
  5. @property (strong, nonatomic) Wilddog *ref;
  6. // Outlets
  7. @property (weak, nonatomic) IBOutlet WKInterfaceLabel *labelUpdate;
  8. @end

Swift

  1. class InterfaceController: WKInterfaceController {
  2. // reference property
  3. var ref: Wilddog!
  4. // Outlets
  5. @IBOutlet weak var awesomeLabel: WKInterfaceLabel!
  6. }

WKInterfaceController生命周期都有一套方法,在这里我们可以初始化一个引用,同步数据,并移除同步事件。使用 awakeWithContext函数,我们可以初始化上面创建的引用。

WKInterfaceController 生命周期

这儿我们将介绍如何将 Wilddog 应用到你的 app 中的每一个生命周期阶段。职能序列被称为一个WKInterfaceController过程通过它的生命周期。

awakeWithContext

WKInterfaceController已加载,awakeWithContext 方法将被调用。因为控制器被加载时,这个方法才调用,所以,这个方法是初始化 Wilddog 引用的最佳地方。

Objective-C

  1. - (void)awakeWithContext:(id)context {
  2. [super awakeWithContext:context];
  3. self.ref = [[Wilddog alloc] initWithUrl:@"https://<your-wilddog-app>.wilddogio.com/updates"];
  4. }

Swift

  1. override func awakeWithContext(context: AnyObject?) {
  2. super.awakeWithContext(context)
  3. ref = Wilddog(url: "https://<your-wilddog-app>.wilddogio.com/updates")
  4. }

willActivate

当视图出现在屏幕上,willActivate方法将被调用。由于这个方法是视图将要显示时,所以这个地方最适合建立 Wilddog 引用。

Objective-C

  1. - (void)willActivate {
  2. [super willActivate];
  3. [self.ref observeEventType:WEventTypeChildAdded withBlock:^(WDataSnapshot *snapshot) {
  4. NSLog(@"%@", snapshot.value);
  5. }];
  6. }

Swift

  1. override func willActivate() {
  2. super.willActivate()
  3. ref.observeEventType(.ChildAdded, withBlock: { (snapshot: WDataSnapshot!) in
  4. println(snapshot.value)
  5. })
  6. }

didDeactivate

当视图在屏幕上消失时,didDeactivate函数被调用。这个地方比较适合移除 Wilddog 的事件监听。

Objective-C

  1. - (void)didDeactivate {
  2. [super didDeactivate];
  3. [self.ref removeAllObservers];
  4. }

Swift

  1. override func didDeactivate() {
  2. super.didDeactivate()
  3. ref.removeAllObservers()
  4. }

保存数据

当用户每次点击该按钮时,我们可以存储一个新的时间戳。

Objective-C

  1. - (IBAction)updateButtonDidTouch {
  2. [[self.ref childByAutoId]setValue:kWilddogServerValueTimestamp];
  3. }

Swift

  1. @IBAction func updateDidTouch() {
  2. ref.childByAutoId().setValue(kWilddogServerValueTimestamp)
  3. }

同步数据

willActivate方法中,我们可以用ref引用去同步数据。

Objective-C

  1. - (void)willActivate {
  2. [super willActivate];
  3. [self.ref observeEventType:WEventTypeChildAdded withBlock:^(WDataSnapshot *snapshot) {
  4. if ([snapshot exists]) {
  5. [self.labelUpdate setText:[snapshot.value stringValue]];
  6. } else {
  7. [self.labelUpdate setText:@"No update"];
  8. }
  9. }];
  10. }

Swift

  1. override func willActivate() {
  2. super.willActivate()
  3. ref.observeEventType(.ChildAdded, withBlock: { (snap: WDataSnapshot!) -> Void in
  4. if snap.exists() {
  5. self.labelUpdate.setText(snap.value as? String)
  6. } else {
  7. self.labelUpdate.setText("No update")
  8. }
  9. })
  10. }

为了确保在屏幕消失后不同步数据,我们需要在didDeactivate方法中删除所有的监听者。

Objective-C

  1. - (void)didDeactivate {
  2. [super didDeactivate];
  3. [self.ref removeAllObservers];
  4. }

Swift

  1. override func didDeactivate() {
  2. super.didDeactivate()
  3. ref.removeAllObservers()
  4. }

运行模拟器

要在模拟器运行 WatchKit app,设置 scheme 为 WatchKit app,点击 run。运行 Apple Watch 模拟器同时,iPhone/iPad 模拟器也会被加载。

模拟器运行完成后,点击 update 按钮。我们的lable将被更新为时间戳字样,Wilddog数据库中数据也被实时更新。

如果没有看到Apple Watch模拟器,可以做如下设置:

打开模拟器 > 选择 Hardware > External Displays > Apple Watch 38mm/42mm

创建 WKTableInterface

WatchKit 提供了一个类似于UITableView的表格控件,它是WKTableInterface

使用Interface.storyboard拖一个table到视图。每个tablerow组成。对于这个tablerow,我们将它起名为TableRow

Objective-C

  1. // TableRow.h
  2. #import <Foundation/Foundation.h>
  3. #import <WatchKit/WatchKit.h>
  4. @interface TableRow : NSObject
  5. @end
  6. // TableRow.m
  7. #import "TableRow.h"
  8. @interface TableRow()
  9. @property (weak, nonatomic) IBOutlet WKInterfaceLabel *labelUpdate;
  10. @end
  11. @implementation TableRow
  12. @end

Swift

  1. //In the WatchKit Extension target
  2. import Foundation
  3. import WatchKit
  4. class TableRow : NSObject {
  5. @IBOutlet weak var labelUpdate: WKInterfaceLabel!
  6. }

该model类必须与Interface.storyboardrow关联。

为了访问在视图上的table interface,我们创建一个InterfaceController

Objective-C

  1. // inside the InterfaceController.m interface
  2. @property (weak, nonatomic) IBOutlet WKInterfaceTable *table;

Swift

  1. // inside the InterfaceController class
  2. @IBOutlet weak var table: WKInterfaceTable!

更新 WKTableInterface

为了将数据更新到WKTableInterface,我们使用.ChildAdded,.ChildRemoved和.ChildChanged事件从Wilddog数据库更新数据。得到数据后,我们将返回一个盛有WDataSnapshot对象的数组。

Objective-C

  1. @property (strong, nonatomic) Wilddog *ref;
  2. @property (strong, nonatomic) NSMutableArray *updates;

Swift

  1. var ref: Wilddog!
  2. var updates: [WDataSnapshot]!

awakeWithContext方法中初始化数组。

Objective-C

  1. - (void)awakeWithContext:(id)context {
  2. [super awakeWithContext:context];
  3. self.ref = [[Wilddog alloc] initWithUrl:@"https://<your-wilddog-app>.wilddogio.com/updates"];
  4. // Initialize the array
  5. self.updates = [[NSMutableArray alloc] init];
  6. }

Swift

  1. override func awakeWithContext(context: AnyObject?) {
  2. super.awakeWithContext(context)
  3. ref = Wilddog(url: "https://<your-wilddog-app>.wilddogio.com/updates")
  4. updates = [WDataSnapshot]()
  5. }

willActivate方法中添加监听者:

.ChildAdded

增加一个.ChildAdded观察者,监听最近增加的数据快照。

Objective-C

  1. // Listen for children added to add new rows to the table
  2. [self.ref observeEventType:WEventTypeChildAdded withBlock:^(WDataSnapshot *snapshot) {
  3. // Add to the local array of snapshots
  4. [self.updates addObject:snapshot];
  5. // Create the index for the NSIndexSet
  6. NSUInteger index = self.updates.count - 1;
  7. NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:index];
  8. // Insert the row into the table
  9. [self.table insertRowsAtIndexes:indexSet withRowType:@"TableRow"];
  10. // Set up the row, check for string and number in snapshot.value
  11. TableRow *row = [self.table rowControllerAtIndex:index];
  12. if ([snapshot.value isKindOfClass:[NSNumber class]]) {
  13. NSNumber *numberSnap = snapshot.value;
  14. [row.labelUpdate setText:[numberSnap stringValue]];
  15. } else if ([snapshot.value isKindOfClass:[NSString class]]){
  16. NSString *stringSnap = snapshot.value;
  17. [row.labelUpdate setText:stringSnap];
  18. }
  19. }];

Swift

  1. // Listen for children added to add new rows to the table
  2. ref.observeEventType(.ChildAdded, withBlock: { [unowned self] (snapshot: WDataSnapshot!) -> Void in
  3. // Add to the local array of snapshots
  4. self.updates.append(snapshot)
  5. // Create the index for the NSIndexSet
  6. var index = self.updates.count - 1
  7. // Insert the row into the table
  8. self.table.insertRowsAtIndexes(NSIndexSet(index: index), withRowType: "TableRow")
  9. // Set up the row
  10. if let row = self.table.rowControllerAtIndex(index) as? TableRow {
  11. row.labelUpdate.setText(snapshot.value.description)
  12. }
  13. })

.ChildRemoved

添加一个.ChildRemoved观察者,监听最近被移除的数据快照。我们先创建一个辅助的findIndexOfSnapshotFromArrayByKey方法:

Objective-C

  1. // Find a snapshot by its key
  2. - (int)findIndexOfSnapshotFromArrayByKey:(NSMutableArray *)array :(NSString *) key {
  3. for (int i=0; i < array.count; i++) {
  4. id item = array[i];
  5. if ([item isKindOfClass:[WDataSnapshot class]]) {
  6. WDataSnapshot *snapshot = item;
  7. if ([snapshot.key isEqualToString: key]) {
  8. return i;
  9. }
  10. }
  11. }
  12. return -1;
  13. }

Swift

  1. // Find a snapshot by its key
  2. func findIndexOfSnapshotFromArrayByKey(array: [WDataSnapshot!], key: String) -> Int? {
  3. for (index, item) in enumerate(array) {
  4. let snapshot = item as WDataSnapshot;
  5. if snapshot.key == key {
  6. return index
  7. }
  8. }
  9. return nil;
  10. }

willActivate方法中添加下面的监听者:

Objective-C

  1. // Listen for children removed to remove rows from the table
  2. [self.ref observeEventType:WEventTypeChildRemoved withBlock:^(WDataSnapshot *snapshot) {
  3. // Find the index of the item being removed in the local array
  4. int index = [self findIndexOfSnapshotFromArrayByKey:self.updates : snapshot.key];
  5. // Create the index for the NSIndexSet
  6. NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:index];
  7. // Remove from the local array and from the table
  8. [self.updates removeObjectAtIndex:index];
  9. [self.table removeRowsAtIndexes:indexSet];
  10. }];

Swift

  1. // Listen for children removed to remove rows from the table
  2. ref.observeEventType(.ChildRemoved, withBlock: { [unowned self] (snapshot: WDataSnapshot!) in
  3. // Find the index of the item being removed in the local array
  4. if let indexToRemove = self.findIndexOfSnapshotFromArrayByKey(self.updates, keysnapshot.key) {
  5. // Remove from the local array and from the table
  6. self.updates.removeAtIndex(indexToRemove)
  7. self.table.removeRowsAtIndexes(NSIndexSet(index: indexToRemove))
  8. }
  9. })

.ChildChanged

添加一个.ChildChanged监听者,监听已变化的数据快照:

Objective-C

  1. // Listen for children whose values have changed and re-render the row
  2. [self.ref observeEventType:WEventTypeChildChanged withBlock:^(WDataSnapshot *snapshot) {
  3. // Find the index of the item that has changed
  4. int indexToChange = [self findIndexOfSnapshotFromArrayByKey:self.updates : snapshot.key];
  5. // Create the index for the NSIndexSet
  6. NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:indexToChange];
  7. // Replace the old snapshot with the new one
  8. self.updates[indexToChange] = snapshot;
  9. // Remove the old row
  10. [self.table removeRowsAtIndexes:indexSet];
  11. // Insert the new row
  12. [self.table insertRowsAtIndexes:indexSet withRowType:@"TableRow"];
  13. // Set up the row, check for string and number in snapshot.value
  14. TableRow *row = [self.table rowControllerAtIndex:indexToChange];
  15. if ([snapshot.value isKindOfClass:[NSNumber class]]) {
  16. NSNumber *numberSnap = snapshot.value;
  17. [row.labelUpdate setText:[numberSnap stringValue]];
  18. } else if ([snapshot.value isKindOfClass:[NSString class]]){
  19. NSString *stringSnap = snapshot.value;
  20. [row.labelUpdate setText:stringSnap];
  21. }
  22. }];

Swift

  1. // Listen for children whose values have changed and re-render the row
  2. ref.observeEventType(.ChildChanged, withBlock: { [unowned self] (snapshot: WDataSnapshot!) in
  3. // Find the index of the item that has changed
  4. if let indexToChange = self.findIndexOfSnapshotFromArrayByKey(self.updates, key: snapshot.key) {
  5. // Replace the old snapshot with the new one
  6. self.updates[indexToChange] = snapshot
  7. // Remove the old row
  8. self.table.removeRowsAtIndexes(NSIndexSet(index: indexToChange))
  9. // Insert the new row
  10. self.table.insertRowsAtIndexes(NSIndexSet(index: indexToChange), withRowType: "TableRow")
  11. // Set up the row
  12. if let row = self.table.rowControllerAtIndex(indexToChange) as? TableRow {
  13. row.labelUpdate.setText(snapshot.value.description)
  14. }
  15. }
  16. })

用户认证

iOS App Extensions,和 Watch Apps 一样,都是单独的 bundle 。我们可以在NSUserDefaults中存储用户的认证 token。调用authWithCustomToken方法去用户认证。

保存认证token

在 host app 中,用户认证之后,存储用户的 Wilddog auth token 。这是在UIViewController中处理登录的常规方法。

Objective-C

  1. Wilddog *ref = [[Wilddog alloc] initWithUrl:@"https://<your-wilddog-app>.wilddogio.com/"];
  2. NSUserDefaults *defaults = [[NSUserDefaults alloc]initWithSuiteName:@"group.username.SuiteName"];
  3. [ref observeAuthEventWithBlock:^(WAuthData *authData) {
  4. if (authData) {
  5. [defaults setObject:authData.token forKey:@"WAuthDataToken"];
  6. [defaults synchronize];
  7. }
  8. }];

Swift

  1. let defaults = NSUserDefaults(suiteName: "group.username.SuiteName")!
  2. ref.observeAuthEventWithBlock { [unowned self] (authData: WAuthData!) in
  3. if authData != nil {
  4. defaults.setObject(authData.token, forKey: "WAuthDataToken")
  5. defaults.synchronize()
  6. }
  7. }

认证 WatchKit Extension 用户

既然 auth token 已经在上一步中存储,在 WatchKit Extension 中就可以从NSUserDefaults中取出 token。

InterfaceController 控制器的 awakeWithContext 方法中添加以下代码:

Objective-C

  1. - (void)awakeWithContext:(id)context {
  2. [super awakeWithContext:context];
  3. self.ref = [[Wilddog alloc] initWithUrl:@"https://<your-wilddog-app>.wilddogio.com/updates"];
  4. self.updates = [[NSMutableArray alloc] init];
  5. // Use the same suiteName as used in the host app
  6. NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.username.SuiteName"];
  7. // Grab the auth token
  8. NSString *authToken = [defaults objectForKey:@"WAuthDataToken"];
  9. // Authenticate with the token from the NSUserDefaults object
  10. [self.ref authWithCustomToken:authToken withCompletionBlock:^(NSError *error, WAuthData *authData) {
  11. if (authData != nil) {
  12. NSLog(@"Authenticated inside of the Watch App!");
  13. } else {
  14. NSLog(@"Not authenticated");
  15. }
  16. }];
  17. }

Swift

  1. override func awakeWithContext(context: AnyObject?) {
  2. super.awakeWithContext(context)
  3. ref = Wilddog(url: "https://<your-wilddog-app>.wilddogio.com/updates")
  4. updates = [WDataSnapshot]()
  5. // Use the same suiteName as used in the host app
  6. let defaults = NSUserDefaults(suiteName: "group.username.SuiteName")!
  7. // Grab the auth token
  8. let authToken = defaults.objectForKey("WAuthDataToken") as? String
  9. // Authenticate with the token from the NSUserDefaults object
  10. ref.authWithCustomToken(authToken, withCompletionBlock: { [unowned self] (error: NSError!, authData: WAuthData!) in
  11. if authData != nil {
  12. println("Authenticated inside of the Watch App!")
  13. } else {
  14. println("Not authenticated")
  15. }
  16. })
  17. }

只要将 token 从NSUserDefaults中取出来,调用authWithCustomToken方法去登录认证用户。

处理未认证 WatchKit Extension 用户

如果用户是未认证的,他们只会看到一个空白屏幕。如果认证失败,withCompletionBlock方法不会返回一个authData参数。在withCompletionBlock方法中修改以下代码:

Objective-C

  1. // Authenticate with the token from the NSUserDefaults object
  2. [self.ref authWithCustomToken:authToken withCompletionBlock:^(NSError *error, WAuthData *authData) {
  3. if (authData != nil) {
  4. NSLog(@"Authenticated inside of the Watch App!");
  5. } else {
  6. // Create a dummy row
  7. NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:0];
  8. [self.table insertRowsAtIndexes:indexSet withRowType:@"TableRow"];
  9. // Give it a message informing the user to log in
  10. id row = [self.table rowControllerAtIndex:0];
  11. if ([row isKindOfClass:[TableRow class]]) {
  12. TableRow *tableRow = row;
  13. [tableRow.labelUpdate setText:@"Please log in"];
  14. }
  15. }
  16. }];

Swift

  1. ref.authWithCustomToken(authToken, withCompletionBlock: { [unowned self] (error: NSError!, authData:WAuthData!) in
  2. if authData != nil {
  3. println("Authenticated inside of the Watch App!")
  4. } else {
  5. // Create a dummy row
  6. self.table.insertRowsAtIndexes(NSIndexSet(index: 0), withRowType: "TableRow")
  7. if let row = self.table.rowControllerAtIndex(0) as? TableRow {
  8. // Give it a message informing the user to log in
  9. row.labelUpdate.setText("Please log in")
  10. }
  11. }
  12. })

如果authDatanil, label 或者 table row 就会通知用户你还未登录。
在这个例子中,我们用 row 显示用户未登录。

注册 Wilddog

WatchKit 需要 Wilddog 来同步和存储数据。您可以在这里注册一个免费帐户。

支持

如果在使用过程中有任何问题,请提 issue ,我会在 Github 上给予帮助。

相关文档

License

MIT

感谢 Thanks

lib-ios-watchkit is built on and with the aid of several projects. We would like to thank the following projects for helping us achieve our goals:

Open Source:

  • WatchKit WatchKit for Objective-C - Realtime location queries with Firebase