AppleWatch開発のベストプラクティス 〜WatchKit Development Tipsに書かれていることをまとめた〜

# 注意事項
WWDC2015でwatch OS2が発表されましたが、こちらの記事は**watch OS1を対象**に書かれています。watch OS2では該当しない恐れがあるのでご注意ください。

# WatchKit Development Tips

ついにAppleWatchの発売日が明らかになりました。同時にXcode6.2、iOS8.2もリリースされ、AppleWatchの開発がしやすくなりました（とは言え実機がないとなんとも、という感じですが）。

現在のAppleWatchは制約が多く、開発時に注意しておかなければならないことが多くあります。

特にバッテリーがそうですが、これを意識してかWatchKit開発時のTipsを集めたページが用意されています。

> https://developer.apple.com/watchkit/tips/

見てみると、結構大事なことが書かれており、もしかしたら過度にiPhoneと通信を行ったりするアプリはリジェクト対象になってしまうのではないかと懸念したりしています。

## General Guidance

### Animate with image sequences.

**連番の画像でアニメーションさせましょう。**

いわゆるパラパラマンガですね。WatchKitの設計上、処理はiPhone側で行うことになるので、CoreGraphicsで作るような凝ったアニメーションをやろうとすると画像をいちいち転送しなくてはなりません。

AppleWatch側に画像をキャッシュさせてアニメーションさせましょう。この辺のアニメーション作成はAfterEffectsが簡単にできますね。

### Store image sequences on Apple Watch.

**アニメーションに使用する画像はAppleWatch側に置く。**

[`WKInterfaceDevice`](https://developer.apple.com/library/prerelease/ios/documentation/WatchKit/Reference/WKInterfaceDevice_class/index.html)というクラスで画像のキャッシュをさせることができます。

```swift
func addCachedImage(_ image: UIImage, name name: String) -> Bool
```

画像をAppleWatch側にキャッシュさせておくことで読み込む時間を短縮させることができます。

テレビなどと同じフレームレートで1秒間アニメーションさせるには30フレーム、つまり30枚の画像が必要になるので、転送コストは結構なものになります。

デベロッパーがこの辺を考慮していないと無駄に電力を消費するアプリを作ってしまいますし、デザイナーとうまく協力して適切なフレーム数を割り出していきたいところですね。無駄に多くても意味がないので。

### Use a settings bundle.

**Settings Bundleを使いましょう。**

iOS8.2からAppleWatch用の設定をするアプリが追加されました。
Settings Bundleを追加すると、そのアプリでAppleWatchアプリの設定ができるようになるそうです。

![AppleWatch設定アプリ.jpg](https://qiita-image-store.s3.amazonaws.com/0/45525/0c62d177-55da-6199-1d82-badb3fd98f19.jpeg)

AppleWatch側で細かな設定をユーザーにしてもらうのは避けなさい、ということだと思います。

### Go straight to dictation.

**すぐに音声入力を。**

ここ、文章を読んだだけだとよくわからなかったのですがAPI見たら大体理解できました。でもちょっと自信ないです。

> If you are using the text input controller to enable dictation, send people directly to dictation without an intermediary sheet by passing no suggestions and setting the text input mode to plain.

音声入力を有効にするためにTextInputControllerを使用するのであれば、`suggestions`を渡さず`plain`モードに設定することで、すぐに音声入力することができます。

`suggestions`というのはテキスト入力画面を表示させるときに渡すパラメータの一つです。`NSString`の配列で渡してユーザーに選択してほしい文字列を表示させます。

もし音声入力だけで良いのであれば`nil`を渡せばいいということです。

```swift
func presentTextInputControllerWithSuggestions(_ suggestions: [AnyObject]?,
                              allowedInputMode inputMode: WKTextInputMode,
                                    completion completion: ([AnyObject]!) -> Void)
```

第2引数がモードになっており`WKTextInputMode`には3つのモードがあります。

```swift
enum WKTextInputMode : Int {
    case Plain // Text from dictation and suggestions only. Do not allow emoji of any kind.

    case AllowEmoji // Text from dictation and suggestions plus non animated emoji.
    case AllowAnimatedEmoji // Text from dictation and suggestions plus both animated and non 
}
```

`Plain`以外を指定すると、以下の画像のように絵文字のオプションができ、マイクアイコンを押してからじゃないと音声入力ができません（ということだと解釈しました）。

![dictation.png](https://qiita-image-store.s3.amazonaws.com/0/45525/19bd1a69-62b1-a4ee-1fff-c29982c9d3dd.png)

音声入力だけしてほしいときは`suggestions`になにも渡さず、`Plain`を指定しましょう。そうすることで無駄なアクションなしに音声入力を開始できます。

あくまでも音声入力のみで良い場合だと思います。

### State restoration.

**状態を回復させましょう。**
`WKInterfaceController`の`didDeactivate:`メソッド内でUIや状態の保存を行いなさいとのこと。復元する場合は`willActivate:`で。

## Performance

### Minimize traffic.

**やりとりを最小限に。**

ここでいうやりとりはiPhoneとAppleWatchの通信のことです。とにかく少なくしなさい、と。

適切に画像をキャッシュさせてiPhoneと通信するたびに画像を読み込まなくても良いようにしましょう。

### Update only what has changed.

**変更があった時だけ更新を。**

データの読み込みは必要なときだけ行うようにすべきとのことです。

すべてのテーブルを更新するのではなく、必要なときに、必要な行にだけ、変更を適用しなさい、といったことが書かれています。


### Load content lazily.

**コンテンツをゆっくりと読み込むこと。**

アプリの立ち上げがスムーズになるよう、表示されない部分の読み込みはユーザーに表示された後に行うようにするべきとのこと。

どうするのかというと、

> By making a dispatch_async call within the WKInterfaceController willActivate: method, you can delay setting specific content until the controller has been displayed.

`dispatch_async`を`willActivate:`メソッド内で使用することによって、コントローラーが表示されるまで特定のコンテンツの設定を遅らせることができる、とのことです。。

### Initialize paginated controllers quickly.

**ページベースのコントローラたちの初期化は素早く。**

AppleWatchでページベースのものを触ったことがないのですが、[こんな感じ](http://natashatherobot.com/watchkit-page-based-navigation/)だと思います。

> To reduce the load time of paginated controllers, complete most of the setup work for each page in the first call to each controller’s willActivate: method and manage state to only setup content the first time it’s called. 

ページベースコントローラの読み込み時間を減らすために、各ページのセットアップを`willActivate:`メソッドでほぼ完了させ、コントローラが呼ばれた時にはコンテンツの準備だけになるよう状態を管理する。

> When paginated controllers are created, init: and awakeWithContext: are called on each page before willActivate: is called on the first page. 

ページベースのコントローラが作られる時、それぞれのページでwillActivate:メソッドの前にinit:とawakeWithContext:が呼ばれる

> The system will wait until all pages are finished doing setup work in init: and awakeWithContext: before displaying the first page to the user.

システムは最初のページを表示させる前に全てのページのinit:とawakeWithContext:で準備が完了されるまで待機するようになっている。

### Simplify controller scenes.

**コントローラーのシーンをシンプルに保つこと。**

> Reduce the number of hidden objects as much as possible to significantly improve load time. For example, five versions of a controller’s layout in a single controller scene will result in all objects being created before the controller is displayed.

読み込み時間を可能な限り改善させるには表示されないオブジェクトの数をなるべく減らすこと。

例えば、一つのシーンの中に5つのコントローラーのレイアウトが存在する場合、コントローラーが表示される前に全てのオブジェクトを読み込むので、その分時間がかかってしまうとのこと。


### Improve load times with fewer table rows.

**テーブルの行数を少なくして読み込み時間を改善させる。**

> Make sure to create the optimal number of rows to display initially, and delay the loading of additional rows until willActivate: is finished. 

初期表示の際は適切な数の行が作成されるよう確認し、追加で読み込む行は`willActivate:`が完了するまで読み込みを遅らせるようにする。


> All table rows are created before the controller is displayed, and the more rows that you define, the longer the controller may take to display.

全てのテーブルの行がコントローラーが表示される前に作成され、たくさんの行が定義されているとコントローラーが表示されるまでにより多くの時間がかかってしまう。


## Communicating with iPhone

AppleWatchの処理はiPhone側で実行されるのでAppleWatchとデータのやり取りをすることが大事になります。

### The iPhone app should do most of the work.

**処理の多くをiPhone側に委ねよう。**

`openParentApplication:reply:`でiPhone側のアプリをバックグラウンドで立ち上げて、WatchKitのデータを立ち上げるようにしなさい、とのことです。

非同期でやり取りする場合はバックグラウンドでうまく処理し、きちんとAppleWatchにデータを返すようにしましょう。

### Share data with App Groups.

**App Groupsでデータを共有させよう。**

`App Groups`と`NSUserDefaults`を使ってiPhoneとWatchKitでデータのやり取りをしなさい、ということです。

CoreDataなどにアクセスする場合は日付情報を付与してアクセスしなさい的なことが書いてありますが、いつ更新されたのかを把握するために日付情報を付与しましょうということだと思います。

### Use Handoff.

**Handoffを使おう。**

iOS8とYosemiteで使用可能になったHandoff。AppleWatchもこれを使用することでiPhoneとの行き来をスムーズにすることができるようです。

GlanceとカスタムNotificationでiPhoneへのディープリンクとしてHandoffが使えるとのことです。


### Share code with frameworks.

**フレームワークでコードを共有させよう。**

WatchKitとiOSアプリの間でコードを共有ささせるためにframeworkを作成しましょう、とのこと。

[Embedded Framework](http://dev.classmethod.jp/references/ios-8-embedded-framework/)を利用して共通して利用する部分を切り出しておきましょう、ということです。

## まとめ

ずらっと並べるとこんな感じ。

* 連番の画像でアニメーションさせましょう。
* アニメーションに使用する画像はAppleWatch側に置く。
* Settings Bundleを使いましょう。
* すぐに音声入力を。
* 状態を回復させましょう。
* やりとりを最小限に。
* 変更があった時だけ更新を。
* コンテンツをゆっくりと読み込むこと。
* ページベースのコントローラたちの初期化は素早く。
* コントローラーのシーンをシンプルに保つこと。
* テーブルの行数を少なくして読み込み時間を改善させる。
* 処理の多くをiPhone側に委ねよう。
* App Groupsでデータを共有させよう。
* Handoffを使おう。
* フレームワークでコードを共有させよう。


[AppleWatchのバッテリーは18時間持つ](http://www.gizmodo.jp/2015/03/applewatch18_applelive.html)ということですが、Appleのサイトを見ると注釈がつきまくっていますし、こうやってTipsも出しているので、最初にも述べましたが、やたらiPhoneから画像を受け取らなければならないようなアプリはリジェクトの対象になるかもしれませんね（個人の予想です）。

きちんと調べたつもりですが、間違いや指摘事項などがあればご指摘いただけると幸いです。