4月24日のAppleWatch発売に向けてWatchKitを使用したアプリの作り方を紹介します。
簡単なWatchKitの起動の方法から、
WatchKitからiPhoneにリクエストを投げて、iPhone側で処理を行いWatchKitに応答する方法を説明します。
また、現時点で分かっているAppleWatchで可能なこと/不可能なことをまとめました。
実機ではできるが、シミュレーターではできないこともまとめました。
(2015年4月6日 現在)
#AppleWatchアプリの種類
AppleWatchアプリには3種類あります。
・WatchKit Apps
・Glance
・Notification
WatchKit Apps:メインとなるAppleWatch内で操作できるアプリ
Glance:スクロールなどはできない一枚絵の画面。タップするとWatchKit Appsへ遷移。
Notification:プッシュ通知やローカル通知を表示する
公式のビデオガイドを見ると、
起動画面から上にスワイプするとGlanceが表示され、
起動画面から下にスワイプするとNotificationが表示されるようです。
https://www.apple.com/jp/watch/guided-tours/
本記事ではWatchKit Appsの作り方を紹介します。
#WatchKit Appの作り方
AppleWatchのアプリは単体では作れません。
iPhone本体アプリが必要です。
既存のXcodeプロジェクトにWatchKit Appsを追加します。
①「File」→「New」→「Target」を選択
④WatchKit Appsを起動できるように新しいスキームを追加します。
⑤ファイルが追加されていることを確認
「(プロジェクト名) WatchKitExtension」と「(プロジェクト名) WatchKit App」のフォルダが追加されているはずです。
⑥WatchKit Appsを起動
コードに何も追加していませんが、まずはWatchKit Appsをシミュレーターで動かしてみます。
⑦AppleWatchシミュレーターの追加
デフォルトではiPhoneシミュレーターしか起動しないので、手動でAppleWatchシミュレーターを起動します。
iOSシミュレーターをクリックしてカーソルを合わせて、
「Hardware」→「External Displays」→「Apple Watch - 38mm」を選択。
⑧AppleWatchシミュレーターの起動を確認
AppleWatchの画面に何も追加していませんので、今は何も表示されません。
#AppleWatchにボタンとラベルの追加
iOSアプリと同じ要領でStoryboardにUIを追加します。
追加したボタンが押された時の処理の追加はiOSと同様に、画面に対応するInterfaceController.mに線を引っ張ってリンクさせます。
(詳細は割愛させて頂きます。)
ビルドすると追加されたUIが表示されます。
#AppleWatchとiPhoneとの通信
AppleWatchは単体では通信の仕組みなどを持っておらず、iPhoneに処理を依頼してデータを受け取ります。
###AppleWatch側からのリクエスト
画面が表示されたときにiPhone側にリクエストを投げるサンプルコードを紹介します。
iPhoneとの情報のやり取りはNSDictionaryに開発者が定義した中身でやり取りします。
WatchKit AppからはWKInterfaceController -openParentApplication:reply:を使用します。
※このメソッドを受け取ったからといってiPhoneアプリを開くことはできません。
(=フォアグラウンドにはなりません。)
iPhone側は状態を維持したままになります。
- (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を持たせます。
@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などから応答を返すことが可能です。
#import <Foundation/Foundation.h>
@interface WatchKitManager : NSObject
- (void)handleWatchKitRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply;
@end
#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からのメッセージを表示します。
以上が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は1:1でのみしか使用できません。
###・iPadではAppleWatchを使えない
iPadではAppleWatchが使用できません。
iPadでiPhoneアプリを動かせますが、その場合にどうなるのかは不明です。
実機の発売が楽しみですね。
#(実機ではできるけど)シミュレーターではできないこと
###・iPhone端末ロック中のAppleWatchアプリの動作
シミュレーターではiPhone側がロックされているときにAppleWatchの表示が、
「Unlock to activate」となり、操作ができません。
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