[watch OS1] WatchKitの初期表示画面を動的に変更する方法

# watch OS1 専用
**ここで書いていることはwatch OS1のためのものです。**watch OS2の場合は`WKExtensionDelegate`というクラスがあるので、そちらでハンドリング可能だと思います(未検証)。**

# AppleWatchにはAppDelegateがない

WatchKitで画面を作成するとこんな感じになると思います(GlanceとNotificationはオプションなのでない場合もある)。

![画面構成.png](https://qiita-image-store.s3.amazonaws.com/0/45525/f9175b85-6e0e-8596-bfd0-521e368e1017.png)

ユーザーの状態などによってWatchAppに表示する画面を切り替えたい場合があったりします。ユーザーがログイン済だったらタイムラインを表示、未ログインだったらログインページを表示する、みたいな場合です。

iPhoneだったらAppDelegateなどでうまく処理して表示の切り替えを行うことになるのですが、残念ながらAppleWatchにはAppDelegateがないので、Storyboardで`is Initial Controller`がつけられた`WKInterfaceController`が最初に呼ばれるようになります。

![initial.png](https://qiita-image-store.s3.amazonaws.com/0/45525/569a6b37-ad13-0a4a-bc87-539d1710d1ed.png)

## 動的にコントローラーを変更する役割のWKInterfaceControllerを用意する

最初、これを解決する方法が分からず「どうしようもないFrameworkだな」くらいに思っていたのですが、`WKInterfaceController`にAppDelegateに似た役割を持たせれば解決可能でした。

`WKInterfaceController`には`reloadRootControllersWithNames`というクラスメソッドが用意されており、第一引数にStoryboardで設定した各コントローラーのidentifierの配列を、第二引数の`contexts`には各コントローラーに渡したいデータを渡します。

```swift

class func reloadRootControllersWithNames(_ names: [AnyObject],
                                 contexts contexts: [AnyObject]?)

```

このメソッドを利用すると、ルートのコントローラーから全て置き換わるため、ユーザーの状態によって異なる画面構成を提供することも可能になります。

## サンプル

以下は`EntryPointInterfaceController`をinitial Controllerにし、iOS8.2のiPhoneからプリインストールされているApple Watchアプリに設定項目を追加します(Settings BundleをiPhoneアプリのターゲットに追加して設定項目を整えればOK)。

Settings Bundleの設定はこんな感じです（最下部の段が空欄になっていますがAppGroupsの設定を忘れないようにしてください[^1]）。

![settings_bundle.png](https://qiita-image-store.s3.amazonaws.com/0/45525/c76046a8-6634-d9e9-a068-2f186e3f181d.png)

iPhone側のAppleWatchアプリではこのように表示されます。

![apple-watch_setting1.png](https://qiita-image-store.s3.amazonaws.com/0/45525/c045e27b-259a-bfa9-a7b0-c0b489200b5c.png)

![apple-watch_setting2.png](https://qiita-image-store.s3.amazonaws.com/0/45525/24789997-09da-0206-db3d-a0f5a07c0d8a.png)


iPhoneのAppleWatchアプリで設定した項目を`NSUserDefaults`から読み込んでWatchAppの画面を切り替えるためのサンプルコードがこちらです。

```swift
class EntryPointInterfaceController: WKInterfaceController {

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        
        let defaults: NSUserDefaults = NSUserDefaults(suiteName: "/* AppGroupsのidentifierを入れてください */")!
        let typeString: NSString? = defaults.valueForKey("screen") as? NSString
        
        if let type = typeString {
            
            if type.isEqualToString("login-screen") {
                WKInterfaceController.reloadRootControllersWithNames(["login"], contexts: nil)
            } else {
                WKInterfaceController.reloadRootControllersWithNames(["userInfo"], contexts: nil)
            }
            
        } else {
                WKInterfaceController.reloadRootControllersWithNames(["login"], contexts: nil)
        }

    }

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }
}
```

なお、この`EntryPointInterfaceController`は`awakeWithContext`メソッド内でルートコントローラーを切り替える処理を行っているので画面に表示されることはありません。

もしなにかしら表示させたいのであれば`willActivate()`メソッド内でNSTimerなど少し時間をおいて`reloadRootControllersWithNames`メソッドを呼び出せば良いと思います。

そのほかベストプラクティスとAppleがうたっている手法についてもまとめてあるので、AppleWatch開発をしている方はご覧になってみてください。

> [AppleWatch開発のベストプラクティス 〜WatchKit Development Tipsに書かれていることをまとめた〜](http://qiita.com/mo_to_44/items/5e808a681b730585813c)


## まとめ

現時点で実機でのテストができませんし、なかなか挙動が見えないのが辛いところですが、いろいろ開発していると足りない部分をうまく補って自然なように見せる工夫がAppleWatchには必要だと感じます。

バッテリー問題が足を引っ張っていろいろな制限がかかっていると思うので、将来的にはこういった苦しい工夫をしなくても良くなるのではないかと思いますが、新しいデバイスのアプリを開発する醍醐味ってこういうところにあるんじゃないかな〜なんて思って楽しんで開発しています。

デベロッパーそれぞれが色々な工夫を施して出してくるはずなので（というかせざるを得ないのですが）、発売するまでにどれくらいのアプリが対応されるか楽しみですね。

[^1]: WatchAppはiPhoneアプリのExtensionなのでAppGroupsの設定が必須になります。プロジェクト内の各ターゲットはもちろん、Settings Bundle、`NSUserDefaults`を生成する際もAppGroupsの設定を忘れないようにしてください。