More than 1 year has passed since last update.

WatchKit は非常に限られたAPIしか用意されてないので、一見できないことだらけです。が、いざやってみると、限られたクラスをうまく使えば、意外とできることが多くあります

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

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

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

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

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

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

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

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

watch_ui.gif

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

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

  • フレームごとの UIImage を生成して WKInterfaceImage の setImage で転送
  • というのを NSTimer で回す
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
}

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

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

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

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

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: します。

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

カスタムフォント

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

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

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

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

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

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

origin 調整

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

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

角丸

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

テーブルセルの高さ変更

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

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

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

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

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

WKInterfaceButton のカスタマイズ

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

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

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

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

indicator.jpg

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

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

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

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

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

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

(つづく)

随時更新予定!

参考記事

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.