SwiftでCAAnimatonを使ったカスタムUIを作ってみました。

コードはこちらです。
以下に今回使ったCAAnimatonのTipsを2点ほどメモしておきます。
CALayerにアニメーションを設定
このタブビューのUIは以下のような2つのCALayerを使って作られています。
これらは UIBezierPath で切れ込みが入ったタブの形を描画し、 CALayer の path に設定して作っています。
タブがタップされた際は、この2つの CALayerの path を CAAnimaton でアニメーションさせます。
実装は以下のようになります。
// CALayerのpathをアニメーションさせたいのでkeyには "path" を指定します
let animation = CABasicAnimation(keyPath: "path")
animation.duration = 0.25
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
// アニメーションが終わっても元に戻らないようにremovedOnCompletionをfalseに設定しておきます
animation.removedOnCompletion = false
// タップされたタブに切れ込みの位置を動かした形をUIBezierPathで再描画してtoValueに設定
animation.toValue = tabLayerPath()
// tabLayerにアニメーションを追加します
tabLayer?.addAnimation(animation, forKey: "tabAnimation")
// 選択されたタブも同様にtoValueに値を設定してlabelLayerにアニメーションを追加します(animationは使いまわしてtoValueだけ書き換えました)
animation.toValue = labelLayerPath()
labelLayer?.addAnimation(animation, forKey: "labelAnimation")
アニメーション完了時のコールバックを設定する
次にコールバックの設定について書きます。
コールバックの中では以下の2つ処理を行います。
-
tabLayerとlabelLayerからCABasicAnimationを取り除く -
tabLayer.pathとlabelLayer.pathに値をセット
1.tabLayer と labelLayer から CABasicAnimation を取り除く
上記の実装例でアニメーションが終わっても元に戻らないように removedOnCompletion を false に設定していましたが、これだと、tabLayer と labelLayer にいつまでも CABasicAnimation が残り続けてしまいます。
なので、アニメーションが完了したら取り除く必要があります。
2.tabLayer.path と labelLayer.path に値をセット
上記の実装例では tabLayer と labelLayer はアニメーションを設定されただけで、 tabLayer.path と labelLayer.path は実際には値が代入されていません。
なので、tabLayer と labelLayer から CABasicAnimation を取り除いてしまうとタブがアニメーション前の形に戻ってしまいます。
アニメーション完了のコールバックの中で tabLayer.path と labelLayer.path に値を代入して、実際の path の値を代入してあげる必要があります。
ではコールバックの設定方法ですが、 CATransaction を使って実装します。
CATransaction#setCompletionBlock の中に処理を書きます。
CATransaction は UIView#animateWithDuration の finished のフラグが無いので、completionの中で、実行した CATransaction のインスタンスの有無を確認することで代替します。
参考:http://qiita.com/inamiy/items/bdc0eb403852178c4ea7
// アニメーション完了のコールバックを設定
CATransaction.setCompletionBlock({
// 各Layerに設定された `CAAnimaton` のインスタンスを取得
let tabAnimation = self.tabLayer?.animationForKey("tabAnimation")
let labelAnimation = self.labelLayer?.animationForKey("labelAnimation")
// `CAAnimaton` のインスタンスの有無でアニメーションの完了を判定する(nilの場合は finished=true に相当する)
if tabAnimation != nil {
// tabLayerからアニメーションを取り除く
self.tabLayer?.removeAnimationForKey("tabAnimation")
// pathをアニメーション後の値にセットする
self.tabLayer?.path = self.tabLayerPath()
}
if labelAnimation != nil {
// labelLayerについてもtabLayerと同様にコールバック処理を行う
self.labelLayer?.removeAnimationForKey("labelAnimation")
self.labelLayer?.path = self.labelLayerPath()
}
})
これでTipsは終わりです。
特に難しいわけでもないので書くことも少なかったですが、せっかく作ったのでQiitaに書いてみました。
この記事に載せたサンプルコードはほんの一部なので、実際のコードの方もよければ動かしてみてください。

