WatchKit のカスタムUI実現方法のまとめ

WatchKit は非常に限られたAPIしか用意されてないので、[一見できないことだらけ](http://qiita.com/shu223/items/8ac4883105647db41b99)です。が、いざやってみると、限られたクラスをうまく使えば、[意外とできることが多くあります](http://d.hatena.ne.jp/shu223/20150214/1423901142)。

この記事では**いろいろなカスタムUIの作り方**を随時まとめていきたいと思います。

##円形のインジケータ的なUI／アニメーション

Appleによるヒューマンインターフェースガイドラインに載ってたこういうUIですが、

![](http://f.st-hatena.com/images/fotolife/s/shu223/20150214/20150214162301.png)


Core Graphics や UIBezierPath を用いて Watch App 側で動的に描画を行うことは残念ながらできません。

で、どうするのかというと、Apple による公式サンプル『Lister』に正解が示されています。

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/d19178e6-214f-e025-3abf-3aef69cb549d.png" width="500">


この Asset Catalog のスクショを見ればお察しの通り、Appleも**普通に連番アニメーションで実現**しているようです。

```swift:swift
self.circularProgressImage.setImageNamed("progress-")
self.circularProgressImage.startAnimatingWithImagesInRange(
    NSMakeRange(0, 359),
    duration: 2.0,
    repeatCount: 0)
```

（360枚の1度ずつの連番画像を用いてアニメーション）

![watch_ui.gif](https://qiita-image-store.s3.amazonaws.com/0/3180/a69dce6e-5a97-6e92-9abf-2d35b07e93cc.gif)


##動的なリソースを用いたアニメーション

WatchKit App 側に入れてある「Staticな」リソースを使用するアニメーションは `setImageNamed:` と `startAnimatingWithImagesInRange:` を使えば高速にアニメーションできるのですが、**WatchKit App の Asset Catalog にない「Dynamicな」リソースを使用**する場合、

- フレームごとの UIImage を生成して WKInterfaceImage の `setImage` で転送
- というのを NSTimer で回す

```swift:swift
// NGパターン
func onUpdate(timer: NSTimer) {
    
    let fileName = String(format: "frame_%d", self.currentIndex)
    let image = UIImage(named: fileName)
    self.image.setImage(image)
    
    self.currentIndex = currentIndex < kNumberOfFrames - 1 ? currentIndex + 1 : 0
}
```

というやり方だと、**非常にパフォーマンスが悪く**なります。

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/684896f3-ceb3-f3fd-344a-ef61728929a6.gif" width="240">

転送か描画のどちがかわかりませんが、全然間に合っていません。


じゃあキャッシュを使うか、というのが思いつくのですが、**フレームごとの画像をキャッシュするのは非推奨**とプログラミングガイドに明記されています（※アニメーションをキャッシュすること自体が問題というわけではない）。

**プログラミングガイドに示されている正解が、「Animated Image」を利用する方法**です。

```swift:swift
var images: [UIImage] = []
for var i=0; i<kNumberOfFrames; i++ {
    let fileName = String(format: "yusha_%d", i);
    let image = UIImage(named: fileName)
    images.append(image!)
}
let animatedImage = UIImage.animatedImageWithImages(
    images,
    duration: duration)
self.image.setImage(animatedImage)
self.image.startAnimating()
```

UIImage の `animatedImageWithImages:duration:` を利用してひとつの Animated な UIImage オブジェクトを生成してから `setImage:` します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/8f3d8bd3-4ada-dfba-7675-d1b9b65a5e42.gif" width="240">


かなりスムーズにアニメーションします。（画像の大きさにもよるが、やってみた感覚としては30fpsは大丈夫そう。※計測コードを仕込んでも結局Extension側のパフォーマンスしか測れないので、Watch側の実測値はわからない）


##カスタムフォント

**iOSと同様の手順で実現可能**です。

- WatchKit App のバンドルにフォントファイルを入れる
- WatchKit App の Info.plist 編集

<img src="http://f.st-hatena.com/images/fotolife/s/shu223/20150214/20150214162302.png" width="280">


##インターフェースのオーバーレイ

WKInterfaceObject は、**UIView の subview 的にインターフェース同士を重ねられません**。

じゃあインターフェースをオーバーレイさせてUIを構成できないのかというとそうでもなく、<b>WKInterfaceGroup に設定できる背景画像（`setBackgroundImage:`）を利用</b>すれば、こういうことはできます。

<img src="http://f.st-hatena.com/images/fotolife/s/shu223/20150214/20150214162303.png" width="280">

上記画像では、背景画像に、WKInterfaceLabel や、複数の WKInterfaceImage を重ねています。


##origin 調整

WKInterfaceObject には、（UIView における）`frame.origin` 的なプロパティがありません。また UIEdgeInsets 的なものもありません。

が、ここでも WKInterfaceGroup を活用し、ダミーの WKInterfaceObject サブクラスを使ってマージンをとってやれば、それっぽく調整できます。

<img src="http://f.st-hatena.com/images/fotolife/s/shu223/20150214/20150214162304.png" width="280">


##角丸

`WKInterfaceGroup` に `setCornerRadius:` というメソッドがあります。IB だと Radius という欄から設定できます。

画像を角丸にしたい場合、`WKInterfaceImage`を`WKInterfaceGroup`に入れれば良い。

##テーブルセルの高さ変更

普通にコンテンツの高さで決まります。

##38mm と 42mm でレイアウト設定を分ける

IBの + ボタンから設定可能です。やり方としては Size Class の分け方と同じ感じです。

- [Xcode 6 時代のマルチデバイス対応 ～Size Classとベクター画像～](http://d.hatena.ne.jp/shu223/20141002/1412209257)


##画面に対してn等分になるようInterfaceオブジェクトを設置する

IB で "Relative to Container" の比率をセットできます（デフォルトが 1）。

##WKInterfaceButton のカスタマイズ

IB の Content を “Group” に切り替えます（デフォルトは “Text”）


##Apple Watch 標準のローディングアニメーション

ローディングアニメーション、というか Apple 的にいうと Activity Indicator ですが、ご存知の通り WatchKit ではそういうAPIは提供されていません。時間のかかる処理はなるべくやるな、ということのようです。

- [uiactivityindicatorview - Is there an ActivityIndicator in WatchKit for Apple Watch? - Stack Overflow](http://stackoverflow.com/questions/28905131/is-there-an-activityindicator-in-watchkit-for-apple-watch)

でもやっぱりアプリつくってるとそういうのが必要な場面が出てくるわけで、そうなると WKInterfaceImage （および WKInterfaceGroup）で連番アニメーションすることになるのですが、親切にも ** Watch 標準のアニメーションを録画して切り出した連番画像** をダウンロードできるようにしてくれている方がいます。

- [Make Animations for WATCH Using iPhone](http://blog.andydrizen.co.uk/blog/2015/01/03/making-watchkit-animations/)

![indicator.jpg](https://qiita-image-store.s3.amazonaws.com/0/3180/f2e0d149-c4fb-99a9-b19e-38919517f47a.jpeg)

ざっくりですが 0.8 ぐらいの duration がちょうど良さそうでした。


##テーブルで複数種類のセルを使う

（WKInterfaceTable では "セル" という言い方はしてなくて、"row" としか言いませんが、それはさておき）こうやって複数の Row Controller をつくっておいて、

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/c057b783-bd16-d03e-98e6-33453759be0d.png" width="500">

こんな感じで `setRowTypes` で使うセルの row name （Row Controller の identifier）のリストを渡すと、

```swift
let rowTypes = [
    "LabelCell",
    "LabelCell",
    "SwitchCell",
    "SwitchCell",
]
table.setRowTypes(rowTypes)
```

こうやって複数種類のセルを使ったテーブルを実現できます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/34f9e1b5-a6d4-0ef6-5cca-2d1809090cca.png" width="240">



##（つづく） 

随時更新予定！

##参考記事

- [WatchKit もろもろ実機検証](http://d.hatena.ne.jp/shu223/20150424/1429850973)
- [WatchKitを実際にさわってみてわかったこと＠iOSオールスターズ勉強会 #dotsios - Over&Out その後](http://d.hatena.ne.jp/shu223/20150214/1423901142)
- [1分でつくれるAppleWatch対応アプリ & WatchKit 全API解説](http://qiita.com/shu223/items/8ac4883105647db41b99)