Apple Watch の登場を祝い、何人かの友達にこの Apple の新しいプラットフォームから学んだことについて共有してもらうようお願いしました!
パート2はこちらです。
Natasha Murashev
iOS Engineer at Capital One and blogger at Natasha The Robot
私の一番の失敗は Watch からメッセージを送信するときに iOS 側のアプリが起動すると思っていたことです。 WatchKit には iOS 側にメッセージを素早く送るための API があります。それは、バックグランドプロセスで起動するトリガーとなります。バックグランドプロセスを起動させるためには以下の WKInterfaceController のクラスメソッドを呼び出す必要があります。
// WKInterfaceController
class func openParentApplication(userInfo: [NSObject: AnyObject],
reply: (([NSObject: AnyObject]!, NSError!) -> Void)?) -> Bool
// launches containing iOS application on the phone
// userInfo must be non-nil
しかし、ユーザーの iOS アプリは開かないのです! WatchKit ベータ版ではシミュレータの iOS アプリが開きます。なので、こういった機能なのだとすっかり思い込んでいました。実際は iOS アプリがバックグランドで開くだけです。これは、情報を受け取り必要なタスクを実行するには十分です。
Natasha の Apple Watch に関するビデオ: Architecting Your App for the Apple Watch
Brian Gilham
Senior Mobile Developer at TWG and blogger at Five Minute WatchKit
一番の失敗は iOS シミュレータのみで開発を行っていたとき、アプリが BTLE 通信をしているのをよく忘れてしまうことです。シミュレータではレイテンシをわざとリクエストに追加することでこれを再現しようとします。しかし、実際のデバイスではレスポンスタイムは時々かなり遅い場合があります。
アプリの設計、開発しているときに以下の3つの質問を自分に問いかけると良いと思います:
- このビューやアクションのレスポンスタイムが遅い場合は何が起こるか?
- レスポンスが全て受け取れなかった場合はアプリはどのような反応になるか?
- これらの問題を解決するために何ができるか?
この問題の解決策は、それぞれのアプリによって違います。以下にいくつか考えられることを書いておきます:
- 二度以上 API を叩いたり画像を取得することはせず、できるだけキャッシュさせることを考えましょう。もしニュースフィードが15分に一度もアップデートされないのなら、アプリ起動時に毎回 API を叩きにいってもあまり意味のないことだと思います。
- データをユーザーが使用する前にダウンロードするようにしましょう。キャッシュとデータの保存を組み合わせた iOS アプリでバックグランドフェッチを実装するとかなり UX が違ってくると思います。
- ユーザーを待たせてしまう場合、ローディングアニメーションをきちんと表示させてください。ユーザーにアプリが固まったと思わせてはなりません。同じようなことで、リクエストが失敗した場合は UI を更新するかきちんと知らせる必要があります。
Good luck! 何を作るか楽しみにしています!
Conrad Kramer
Creator of Workflow
早い段階で画像を取得する問題がありました。永遠にロードし続け Watch Extension のメモリ使用量が増えクラッシュするというものでした。それからいくつか試行錯誤した結果、以下が画像を Watch に送る私の最善の Tips です。
- スクリーンに表示させるそのままの大きさの画像を作るようにしてください。
- JPEG か PNG に圧縮してください。
- NSData を引数に取る
-[WKInterfaceImage setImageData:]
を使ってください。UIImage を使うより速く、メモリ効率が良いです。
アニメーションイメージが必要な場合は +[UIImage animatedImageWithImages:duration:]
を使ってください。NSKeyedArchiver を使いエンコードし、NSData としてデータを送ってください。
* ここで気を付けることは Watch はこの duration
プロパティを考慮しません。
-[WKInterfaceImage startAnimatingWithImagesInRange:duration:repeatCount:]
で指定された duration
になります。
James Robert
CTO at Media Predict
私の大きな失敗は、アプリのグラフィック要素を Watch で使おうとしたことです。制限がある環境に最適化されたアプローチを取るために一からデザインを考える必要があります。
画像を扱う場合とても注意しなければいけません。- 上記で Conrad も言っているように、満足できるパフォーマンスを得るために JPEG に圧縮した NSData を使用するべきです。しかし私の場合以前は CSS でするような背景色や border radius などのみ使用していました。Watch のスクリーンはかなり小さいです。テキストのスペースを確保するためにできることはやらないといけません。四角いチェックボックスをこのように長細いドットに変更しました。
Curtis Herbert
Independent Developer & Designer
私の一番の失敗はビューの更新はそれほど賢くないということです。これは Slopes を開発中に気付いきました。iOS 開発でやっていたように値が実際に変わっているのかどうかにかかわらず、スクリーンは更新したいときに更新すればいいと考えるのは簡単です。
しかし、注意してください! 全ては帯域に制限がある Bluetooth 経由で行われるということです。
Xcode のコンソールに見ていると WatchKit がビューの更新が重複している箇所を取り除こうとしているのがわかると思います。didDeactivate
が呼ばれた後の View の更新は全て無視されます。Apple は Bluetooth 経由での更新を最小限に止めることに関して、非常にアグレッシブにやっています。しかし WKInterfaceController
や WKView
にはこの状態をトラックし続ける機能(プロパティを取得したりするような明確な理由がある場合を除いて)はそれほど多く提供されていません。
キーとなる View の値(ラベルのテキストや画像の名前や View の hidden の値など)をトラッキングする CachingInterfaceController
を継承して、全てのコントローラを作るようにしています。今のところは少し素朴なやり方ですが WatchKit ラボで半日ほど作業をした結果です。コードは GitHub をご覧ください。かなりコントローラがシンプルになり、イベント駆動のコードを MMWormhole と WFNotificationCenter に移したようにかなりの状態をトラッキングする部分を抽象化できました。
ビューの状態をトラッキングする必要があることを受け入れ、このようなコードを書くことにしたらたくさんの問題が解決しました。
Curtis の Watchkit Communication の動画と ブログ がご覧になれます。
Neil Kimmett
Developer at M&S Digital Labs
WatchKit での一番の失敗は、テキスト要素に多くの余白を含めていたことです。
デスクトップやモバイル向けにデザインしているときは、スクリーンの端とテキストの間に margin を置いています。しかし WatchKit では、黒の背景を使っていると、Watch のフレームは、コンテンツに対して勝手に margin がかけられたように見えます。テキストの右側はスクリーンの端にくっついています。シミュレータではかなり奇妙なことだと思いますが、実際のデバイスでは縁が黒色なので普通です。Watch の限られたリソースの中でより多くのスペースを使えるメリットがあります。
Neil のブログもご覧ください。
Kristina Thai
iOS software engineer at Intuit
私がした一番の失敗は handleWatchKitExtensionRequest
にカスタムクラスのオブジェクトを Dictionary で Watch Extension に渡そうとしたことです。オブジェクトを replyInfo として渡せたのですが plist 形式にシリアライズできずに例外が発生しました。カスタムオブジェクトを渡そうとすると以下のようなエラーメッセージを受け取ります。
Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x61000006f640 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}
カスタムオブジェクトの場合 plist 形式にシリアライズできないので、親の iPhone アプリの handleWatchKitExpensionRequest
の中で reply()
が呼べません。
iPhone アプリと Watch Extension との間でデータを共有するにはいくつか方法があります:
-
データの受け渡しに Dictionary 型はもちろん使うことができます。Watch に表示させるためのカスタムオブジェクトが持ついくつかの String プロパティでした。なので、それらを Dictionary に入れて簡単に渡すことができました。ステータスコードや簡単なメッセージなどを渡すために(String型と他のプリミティブ型)が使えます。少し複雑なデータを渡すために Array 型や Dictionary 型で渡せます。
-
カスタムオブジェクトの全てのプロパティを渡す必要がある場合 App Groups をセットして、必要に応じて
NSUserDefaults
かNSFileManager
のどちらかを使えば良いです。(注意すること:NSFileManager
を使って SharedContainer に読み書きする場合は、データ破損することを防ぐ必要があります) -
NSKeyedArchiver
でカスタムオブジェクトをアーカイブし Directory の中に入れて渡します。以下のようなコードです:
iPhone 側で Dictionary を作ります。
NSMutableDictionary *reply = [NSMutableDictionary new];
MyCustomObject *myObject = <something you need to send>;
reply[@"myKey"] = [NSKeyedArchiver archivedDataWithRootObject: myObject];
NSAttributedString *myString = <some attributed string>;
reply[@"otherKey"] = [NSKeyedArchiver archivedDataWithRootObject: myString];
Watch 側で元に戻します。
NSData *objectData = replyInfo[@"myKey"];
MyCustomObject *myObject = [NSKeyedUnarchiver unarchiveObjectWithData: objectData];
NSData *stringData = replyInfo[@"otherKey"];
NSAttributedString *myString = [NSKeyedUnarchiver unarchiveObjectWithData: stringData];
Brian Montz コードを共有してくれてありがとう!
一番大事なこと: どの情報が必要なのか特定すること。そして、その情報を WatchKit Extension と iPhone アプリ間で共有する最善の方法を決めることです。
Kristina の WatchKit のチュートリアル と ツイッターは @kristinathai です。
Apple Watchがまだ出荷されてないことを恐れないで下さい。変に聞こえますが、時にアプリは他のスクリーンでは、想像している通りに動かないときがあります。Watchアプリのテスト環境はまだ提供されていません。(Cedar を使えばできるとも聞きましたが)はっきりとした契約による設計がされていないコードがたくさん世の中にリリースされることになると���うことです。例えば、他の開発者が変更を加えたいとすれば、影響を与えるコンテキスト全てを把握する必要があります。クールで新しいものであると対照的に、私の Watch アプリはバ���が出るぐらいには大きい規模のものです。そういった理由で、必ずテストが必要という意味ではありません。
皆さんありがとうございました! 明日を楽しみにしておきましょう!
About the content
This content has been published here with the express permission of the author.