Apple watchでジャイロと加速度を取得する

# 前提

+ watchOS 4.3.2
+ Xcode 10.1
+ iPhone 12.1.2
+ Apple Watch series 1


初版のApple watchが出た頃にジャイロの値を取ろうとしたが失敗した（APIは存在するが必ず固定値が返っていた）件の再チャレンジです。

[watchOS 3 で「アレ」は本当にできるようになったのか？](https://qiita.com/shu223/items/31b1b453ee1740d3eaf5)によれば、watchOS 3以降ではジャイロの値が取れるようになっているらしいです。


# tl;dr

https://gist.github.com/izugch73/f66af9c319990e99e9abdaceebe106a8



# 実装

WatchOS1の頃触ってから久しぶりにWatch Appの開発だったので、そもそもの作り方や考え方からやりなおします。

## 基本的なApple watchアプリの作り方


+ [簡単な Apple Watch アプリを初めて作ってみる](https://ez-net.jp/article/8C/ylktGR5J/5ZZKjSNrNQac/)

記事はだいぶ昔ですが内容は今でも通用します。
その他、だいたい「[アレ](https://qiita.com/shu223/items/31b1b453ee1740d3eaf5)」の記事の通りですが、躓いたポイントをメモしておきます。

## ハンドラのエラーの型が違う

[CMDeviceMotionHandlerの第2引数の型はErrorです](https://developer.apple.com/documentation/coremotion/cmdevicemotionhandler)。
時代も移り変わり、Objective-Cの遺産とも言えるNSErrorは使わなくなったみたいです。


```swift:GyroscopeInterfaceController

    override func willActivate() {
        super.willActivate()
        
        let handler: CMDeviceMotionHandler = {(motion: CMDeviceMotion?, error: Error?) -> Void in
            self.rotLabelX.setText(String(format: "%.2f", motion!.rotationRate.x))
            self.rotLabelY.setText(String(format: "%.2f", motion!.rotationRate.y))
            self.rotLabelZ.setText(String(format: "%.2f", motion!.rotationRate.z))
        }

        if motionManager.isDeviceMotionAvailable {
            motionManager.startDeviceMotionUpdates(to: OperationQueue.current!, withHandler: handler)
        }
        else
        {
            rotLabelX.setText("not available gyro")
            rotLabelY.setText("not available gyro")
            rotLabelZ.setText("not available gyro")
        }
		
    }

```

詳しくはこちら → [モーションセンサーの値が取得できない](http://yatsurecreate.com/2017/03/31/swift%E3%81%A7%E3%83%A2%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC%E3%81%AE%E5%80%A4%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%A7%E3%82%A8/)

[githubのコードは直ってた](https://github.com/shu223/watchOS-2-Sampler/commit/dcc46be2c30a5ca9b8297f4b8eb0939997582a67#diff-1117d9b18b9374ff5e5e5b5fc33ad357)ので、プロはこんなところでは躓かないと思いますが、一応。


## ビルドバージョンが高すぎて実行時にコケる

Deployment Targetをちゃんと4.3にしているのに、5系でビルドされてるからApple watchにインストールできないよと怒られる。

よく見るとExtensionだった。

![スクリーンショット 2019-02-21 13.52.45.png](https://qiita-image-store.s3.amazonaws.com/0/178486/4c0f5d84-9d57-16d5-e5d9-fa0fd15ff6da.png)

ちゃんとWatch Appのターゲットを適切な値にしましょう。

![スクリーンショット 2019-02-21 15.10.12.png](https://qiita-image-store.s3.amazonaws.com/0/178486/cfc8fbbd-b25f-1bb6-8be7-5716a43acda0.png)


## クラスが足りなくて実行時にコケる

Watchアプリを作ってる人からしたら常識なのかもしれませんが、`GyroscopeInterfaceController.swift`という新しいInterfaceControllerクラスを作ったのに、Extensionに追加してなくてコケました。

![スクリーンショット 2019-02-21 15.19.49.png](https://qiita-image-store.s3.amazonaws.com/0/178486/57798842-7478-f20c-7077-5674905f56e5.png)

```:ErrorLog

2019-02-21 15:18:09.519384+0900 WatchKitApp Extension[678:3114357] [default] -[SPRemoteInterface createViewController:className:properties:contextID:info:gestureDescriptions:clientIdentifier:]:3418: Couldn't instantiate class _TtC21WatchKitApp_Extension28GyroscopeInterfaceController
2019-02-21 15:18:09.521782+0900 WatchKitApp Extension[678:3114357] [default] -[SPRemoteInterface createViewController:className:properties:contextID:info:gestureDescriptions:clientIdentifier:]:3419: Critical failure. Simulating crash: Condition failed:"NO". Couldn't instantiate class _TtC21WatchKitApp_Extension28GyroscopeInterfaceController
(lldb) 

```

解決するには、自分で作ったクラスをExtensionのCompile Sourcesに追加します。

![スクリーンショット 2019-02-21 15.16.30.png](https://qiita-image-store.s3.amazonaws.com/0/178486/1661a992-5075-a37d-7b90-b93a796b6bf9.png)


## クラスが足りなくて実行時にコケる ２

実は上記だけではだめで、storyboardのCustom classのロード元をextensionにしないといけません。

![スクリーンショット 2019-02-21 15.31.52.png](https://qiita-image-store.s3.amazonaws.com/0/178486/974f21b1-d100-d86e-0f70-6ce45f848d5b.png)

このModuleが非Extensionだと、同じように実行時にクラスが足りないと言ってコケます。

上のようにExtentionのCompile Sourcesに新しいInterfaceControllerを追加すると、ここでExtentionが選択できるので、そちらを選びましょう。

![スクリーンショット 2019-02-21 15.38.05.png](https://qiita-image-store.s3.amazonaws.com/0/178486/9e18b821-c6c0-5aa2-54cd-f15b2475c952.png)

これで動きます。たぶん。


### ていうかExtensionって何…

そもそもApple watch AppというのはiPhoneアプリのおまけのようなもので、処理はほとんどせずに、iPhoneアプリのViewとなることが想定されています。
その関係性を前提として、Apple watchから見たiPhoneの方（自分がViewであって、実処理をする側）の方をExtensionという、みたいです。
<Img src=https://qiita-image-store.s3.amazonaws.com/0/178486/a58e463d-0b2b-5411-8494-bee6d15ebd53.png width=600>
https://noumenon-th.net/webstrategy/2014/12/11/watchkit/


...というのが初期の思想だったみたいですが、watch自体の性能もだんだん上がってきているためか、現在の相関図ではwatch側にExtensionが存在してますね。
<img src=https://qiita-image-store.s3.amazonaws.com/0/178486/5edaed4e-aaea-ae28-12e1-7e631ee9a18c.png width=600>
> The Watch app bundle contains your app’s storyboards, while the WatchKit extension contains your app’s code and additional resources

https://developer.apple.com/library/archive/documentation/General/Conceptual/WatchKitProgrammingGuide/DesigningaWatchKitApp.html#//apple_ref/doc/uid/TP40014969-CH3-SW1

いずれにせよ、Watch Appのstoryboardは表示のため、Extensionは実処理のためという考え方であって、アプリケーションのコードはExtensionに含まれるべきだということみたいです。
ゆえに、storyboardに紐づくソースは、Watch App側とExtension側でcompileされるよう設定すべきということですね。



# 謝辞

+ watchOS 3 で「アレ」は本当にできるようになったのか？
 + https://qiita.com/shu223/items/31b1b453ee1740d3eaf5
+ 簡単な Apple Watch アプリを初めて作ってみる
 + https://ez-net.jp/article/8C/ylktGR5J/5ZZKjSNrNQac/
+ swiftでモーションセンサーの値を取得する方法でエラーになるのを解決した
 + http://yatsurecreate.com/2017/03/31/swift%E3%81%A7%E3%83%A2%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC%E3%81%AE%E5%80%A4%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%A7%E3%82%A8/
+ The Watch App Architecture 
 + https://developer.apple.com/library/archive/documentation/General/Conceptual/WatchKitProgrammingGuide/DesigningaWatchKitApp.html#//apple_ref/doc/uid/TP40014969-CH3-SW1