WatchKit は非常に限られたAPIしか用意されてないので、一見できないことだらけです。が、いざやってみると、限られたクラスをうまく使えば、意外とできることが多くあります。
この記事ではいろいろなカスタムUIの作り方を随時まとめていきたいと思います。
##円形のインジケータ的なUI/アニメーション
Appleによるヒューマンインターフェースガイドラインに載ってたこういうUIですが、
Core Graphics や UIBezierPath を用いて Watch App 側で動的に描画を行うことは残念ながらできません。
で、どうするのかというと、Apple による公式サンプル『Lister』に正解が示されています。
この Asset Catalog のスクショを見ればお察しの通り、Appleも普通に連番アニメーションで実現しているようです。
self.circularProgressImage.setImageNamed("progress-")
self.circularProgressImage.startAnimatingWithImagesInRange(
NSMakeRange(0, 359),
duration: 2.0,
repeatCount: 0)
(360枚の1度ずつの連番画像を用いてアニメーション)
##動的なリソースを用いたアニメーション
WatchKit App 側に入れてある「Staticな」リソースを使用するアニメーションは setImageNamed:
と startAnimatingWithImagesInRange:
を使えば高速にアニメーションできるのですが、WatchKit App の Asset Catalog にない「Dynamicな」リソースを使用する場合、
- フレームごとの UIImage を生成して WKInterfaceImage の
setImage
で転送 - というのを NSTimer で回す
// 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」を利用する方法です。
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 という欄から設定できます。
画像を角丸にしたい場合、WKInterfaceImage
をWKInterfaceGroup
に入れれば良い。
##テーブルセルの高さ変更
普通にコンテンツの高さで決まります。
##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 標準のアニメーションを録画して切り出した連番画像 をダウンロードできるようにしてくれている方がいます。
ざっくりですが 0.8 ぐらいの duration がちょうど良さそうでした。
##テーブルで複数種類のセルを使う
(WKInterfaceTable では "セル" という言い方はしてなくて、"row" としか言いませんが、それはさておき)こうやって複数の Row Controller をつくっておいて、
こんな感じで setRowTypes
で使うセルの row name (Row Controller の identifier)のリストを渡すと、
let rowTypes = [
"LabelCell",
"LabelCell",
"SwitchCell",
"SwitchCell",
]
table.setRowTypes(rowTypes)
こうやって複数種類のセルを使ったテーブルを実現できます。
##(つづく)
随時更新予定!
##参考記事