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に書いてみました。
この記事に載せたサンプルコードはほんの一部なので、実際のコードの方もよければ動かしてみてください。