AppleWatchアプリのHello WorldからiPhoneとの通信まで開発ノウハウまとめ

4月24日のAppleWatch発売に向けてWatchKitを使用したアプリの作り方を紹介します。
簡単なWatchKitの起動の方法から、
WatchKitからiPhoneにリクエストを投げて、iPhone側で処理を行いWatchKitに応答する方法を説明します。
また、現時点で分かっているAppleWatchで可能なこと/不可能なことをまとめました。
実機ではできるが、シミュレーターではできないこともまとめました。
(2015年4月6日　現在)

![スクリーンショット 2015-04-05 18.36.00.png](https://qiita-image-store.s3.amazonaws.com/0/34488/b6c36fff-62ec-6615-dd91-1ee6320dea47.png)

#AppleWatchアプリの種類
AppleWatchアプリには３種類あります。
・WatchKit Apps
・Glance
・Notification

WatchKit Apps：メインとなるAppleWatch内で操作できるアプリ
Glance：スクロールなどはできない一枚絵の画面。タップするとWatchKit Appsへ遷移。
Notification：プッシュ通知やローカル通知を表示する

![スクリーンショット 2015-04-05 23.50.39.png](https://qiita-image-store.s3.amazonaws.com/0/34488/89a46f7e-ce97-a793-3f4c-3eb14bb21322.png)


公式のビデオガイドを見ると、
起動画面から上にスワイプするとGlanceが表示され、
起動画面から下にスワイプするとNotificationが表示されるようです。
https://www.apple.com/jp/watch/guided-tours/

本記事ではWatchKit Appsの作り方を紹介します。

#WatchKit Appの作り方
AppleWatchのアプリは単体では作れません。
iPhone本体アプリが必要です。
既存のXcodeプロジェクトにWatchKit Appsを追加します。

①「File」→「New」→「Target」を選択

![スクリーンショット 2015-04-05 16.04.59.png](https://qiita-image-store.s3.amazonaws.com/0/34488/ff773491-a403-1d15-ae66-2722f2982cfa.png)

②「Apple Watch」を選択
![スクリーンショット 2015-04-05 16.06.02.png](https://qiita-image-store.s3.amazonaws.com/0/34488/489a3bc4-512b-25cb-d431-b050eb915d54.png)

③デフォルトのままでFinishします。
![スクリーンショット 2015-04-05 16.06.35.png](https://qiita-image-store.s3.amazonaws.com/0/34488/73ae1896-1a3e-c333-a14a-4afbad1312d2.png)

④WatchKit Appsを起動できるように新しいスキームを追加します。
![スクリーンショット 2015-04-05 16.07.00.png](https://qiita-image-store.s3.amazonaws.com/0/34488/5af7639d-9573-5962-087f-95fc444755af.png)

⑤ファイルが追加されていることを確認
「（プロジェクト名） WatchKitExtension」と「(プロジェクト名) WatchKit App」のフォルダが追加されているはずです。
![スクリーンショット 2015-04-05 16.07.31.png](https://qiita-image-store.s3.amazonaws.com/0/34488/d2e2b2e2-5066-c3b7-58f7-3c11e7609948.png)

⑥WatchKit Appsを起動
コードに何も追加していませんが、まずはWatchKit Appsをシミュレーターで動かしてみます。
![スクリーンショット 2015-04-05 17.50.56.png](https://qiita-image-store.s3.amazonaws.com/0/34488/f77dfe5e-874c-7668-30cc-9aca78d2f5e3.png)

⑦AppleWatchシミュレーターの追加
デフォルトではiPhoneシミュレーターしか起動しないので、手動でAppleWatchシミュレーターを起動します。
iOSシミュレーターをクリックしてカーソルを合わせて、
「Hardware」→「External Displays」→「Apple Watch - 38mm」を選択。
![スクリーンショット 2015-04-05 18.54.09.png](https://qiita-image-store.s3.amazonaws.com/0/34488/f7d9925a-2803-4830-a09f-0298660744bf.png)

⑧AppleWatchシミュレーターの起動を確認
AppleWatchの画面に何も追加していませんので、今は何も表示されません。
![スクリーンショット 2015-04-05 18.57.50.png](https://qiita-image-store.s3.amazonaws.com/0/34488/edaf583b-8702-78df-ec02-96b8a2b6b62e.png)

#AppleWatchにボタンとラベルの追加
iOSアプリと同じ要領でStoryboardにUIを追加します。
追加したボタンが押された時の処理の追加はiOSと同様に、画面に対応するInterfaceController.mに線を引っ張ってリンクさせます。
（詳細は割愛させて頂きます。）
![スクリーンショット 2015-04-05 19.06.18.png](https://qiita-image-store.s3.amazonaws.com/0/34488/eeeaacbe-e6b0-e728-0975-55e00623b14f.png)

ビルドすると追加されたUIが表示されます。

![スクリーンショット 2015-04-05 19.11.33.png](https://qiita-image-store.s3.amazonaws.com/0/34488/1424e819-7cab-27bd-3611-369b606a0260.png)


#AppleWatchとiPhoneとの通信
AppleWatchは単体では通信の仕組みなどを持っておらず、iPhoneに処理を依頼してデータを受け取ります。

###AppleWatch側からのリクエスト
画面が表示されたときにiPhone側にリクエストを投げるサンプルコードを紹介します。
iPhoneとの情報のやり取りはNSDictionaryに開発者が定義した中身でやり取りします。
WatchKit AppからはWKInterfaceController -openParentApplication:reply:を使用します。
※このメソッドを受け取ったからといってiPhoneアプリを開くことはできません。
（=フォアグラウンドにはなりません。）
iPhone側は状態を維持したままになります。
 

```objc:Objective-C

- (void)willActivate {
    // 画面が表示されたとき
    [super willActivate];
    
    NSDictionary *userInfo = @{@"command": @"hello"};
    
    // iPhoneにリクエストを投げる
    [WKInterfaceController openParentApplication:userInfo reply:^(NSDictionary *replyInfo, NSError *error) {
        // 結果が返ってきたとき
        if (replyInfo) {

            // エラー時
            NSString *result = replyInfo[@"result"];
            if ([result isEqualToString:@"error"]) {
                // エラー理由の表示
                NSString *message = replyInfo[@"message"];
                if (message) {
                    NSLog(@"error: %@", message);
                    
                }
                return;
            }
            
            // 成功時
            if ([result isEqualToString:@"success"]) {
                // 応答文の表示
                NSString *message = replyInfo[@"message"];
                if (message) {
                    NSLog(@"success: %@", message);
                }
                return;
            }
        }
    }];
}


```

###iPhoneでのAppleWatch Appからのリクエストの受信

iPhone側はリクエストをAppDelegateのapplication:handleWatchKitExtensionRequest:reply:　で受け取ります。
AppDelegateの肥大化を防ぐために、WatchKitManagerクラスを作成しました。
AppDelegateのプロパティにWatchKitManagerを持たせます。

```objc:Objective-C
@interface AppDelegate ()

@property (nonatomic) WatchKitManager *watchKitManager;

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    self.watchKitManager = [WatchKitManager new];
    
    return YES;
}


- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
    
    [self.watchKitManager handleWatchKitRequest:userInfo reply:reply];
}

@end

```

###WatchKitManagerによる処理

WatchKitManagerはAppleWatchからのリクエストを受けて、
iPhone側で通信処理などを行った結果をAppleWatch側に返すために、
応答用のblock文replyをプロパティに保持します。
こうするとことで、delegateメソッドやcompletionHandlerなどから応答を返すことが可能です。

```objc:Objective-C(WatchKitManager.h)
#import <Foundation/Foundation.h>

@interface WatchKitManager : NSObject

- (void)handleWatchKitRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply;

@end

```

```objc:Objective-C(WatchKitManager.m)
#import "WatchKitManager.h"

@interface WatchKitManager ()

// WatchKitManagerがどのタイミングでも応答できるようにblockをプロパティで保持する
@property (nonatomic, copy) void (^reply)(NSDictionary *);

@end

@implementation WatchKitManager

- (void)handleWatchKitRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply {
    
    // エラーハンドリング
    if (!userInfo) {
        NSDictionary *replyInfo = @{@"result": @"error",
                                    @"message": @"userInfo is nil"};
        reply(replyInfo);
        return;
    }
    
    // WatchKitへの応答を自由なタイミングで実行するためにプロパティに保持する
    self.reply = reply;
    
    if (userInfo[@"command"]) {
        if ([userInfo[@"command"] isEqualToString:@"hello"]) {
            
            // 通信や遅延処理をする
            // ...
            // 実行した後にプロパティに保持した応答用のblock文replyを使用する
            NSDictionary *replyInfo = @{@"result": @"success",
                                        @"message": @"hello!!"};
            self.reply(replyInfo);
        }
    }
}

@end


```

###サンプルの起動
WatckKitアプリを起動するとiPhoneに"command:hello"が送られ、
iPhoneは"message:hello!!"を返します。
それを受けたAppleWatch AppがログにiPhoneからのメッセージを表示します。
![スクリーンショット 2015-04-05 19.34.00.png](https://qiita-image-store.s3.amazonaws.com/0/34488/303dac63-bc90-40af-33ac-d84b15f840e8.png)

以上がAppleWatchとiPhoneとの通信の仕方です。

#AppleWatchではできないこと
公式回答に基づいてAppleWatchでできないことをまとめました。

###・iPhoneからAppleWatchにリクエストを送れない
AppleWatchからiPhoneにリクエストを送ることはできますが、
iPhoneからAppleWatchにリクエストを送ることはできません。
◯ AppleWatch → iPhone
✕ AppleWatch ← iPhone

どうしてもiPhoneからAppleWatchに情報を送りたければ、
Local noticaitionを使用する手段はありますが、
AppleWatchのみでなく、iPhoneにも通知が表示されてしまいます。
https://devforums.apple.com/thread/266959

###・AppleWatchからiPhoneアプリをフォアグラウンドにできない
AppleWatchからiPhoneにリクエストを送り、
AppDelegateのhandleWatchKitExtensionRequest:で受け取ることはできますが、
その中でopenUrlによってURLスキームする方法は、バックグラウンドではサポートされていません。
AppleWatchから強制的にiPhoneアプリをフォアグラウンドにすることはできません。
https://devforums.apple.com/message/1119498

###・複数のAppleWacthとiPhoneを連携できない
iPhoneとAppleWatchは１：１でのみしか使用できません。

###・iPadではAppleWatchを使えない
iPadではAppleWatchが使用できません。
iPadでiPhoneアプリを動かせますが、その場合にどうなるのかは不明です。
実機の発売が楽しみですね。


#（実機ではできるけど）シミュレーターではできないこと
###・iPhone端末ロック中のAppleWatchアプリの動作
シミュレーターではiPhone側がロックされているときにAppleWatchの表示が、
「Unlock to activate」となり、操作ができません。

![スクリーンショット 2015-04-05 17.55.55.png](https://qiita-image-store.s3.amazonaws.com/0/34488/ec64c11c-7f95-a730-1342-ae67e84ced30.png)

iPhoneがロックされているとAppleWatchアプリで何もできない？と驚きましたが、
シミュレーターのみの現象のようです。
公式曰く「試験のためだけ。いつか対応する。それがいつかは言えない。ごめんね。」だそうです。
https://devforums.apple.com/message/1092204

###・Local Notificationを受け取れない
Remote Notificationはテスト用のPushNotificationPayload.apnsが使用できますが、
Local Notificationをシミュレーターで使うことは今のところできません。
https://devforums.apple.com/message/1096250

#まとめ
未発売のため不明点の多いWatchKitですが、
手探りで情報収集中です。
（間違い等あればご指摘ください。）
新たに分かったことがあれば更新していきます。


#参考
公式プログラミング　ガイド
https://developer.apple.com/library/ios/documentation/General/Conceptual/WatchKitProgrammingGuide/index.html

公式クラスリファレンス
https://developer.apple.com/library/prerelease/ios/documentation/WatchKit/Reference/WatchKit_framework/

公式 WatchKit Development tips
https://developer.apple.com/watchkit/tips/

Watchkit Advent Calendar
http://qiita.com/advent-calendar/2014/watchkit