LoginSignup
16
17

More than 5 years have passed since last update.

SwiftでCAAnimaton

Posted at

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

コードはこちらです。

以下に今回使ったCAAnimatonのTipsを2点ほどメモしておきます。

CALayerにアニメーションを設定

このタブビューのUIは以下のような2つのCALayerを使って作られています。

  • タブの全体(tablayer)
    whole_tab.png

  • 選択されているタブ(labelLayer)
    selected_tab.png

これらは UIBezierPath で切れ込みが入ったタブの形を描画し、 CALayerpath に設定して作っています。
タブがタップされた際は、この2つの CALayerpathCAAnimaton でアニメーションさせます。
実装は以下のようになります。

// 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つ処理を行います。

  1. tabLayerlabelLayer から CABasicAnimation を取り除く
  2. tabLayer.pathlabelLayer.path に値をセット

1.tabLayerlabelLayer から CABasicAnimation を取り除く
上記の実装例でアニメーションが終わっても元に戻らないように removedOnCompletionfalse に設定していましたが、これだと、tabLayerlabelLayer にいつまでも CABasicAnimation が残り続けてしまいます。
なので、アニメーションが完了したら取り除く必要があります。

2.tabLayer.pathlabelLayer.path に値をセット
上記の実装例では tabLayerlabelLayer はアニメーションを設定されただけで、 tabLayer.pathlabelLayer.path は実際には値が代入されていません。
なので、tabLayerlabelLayer から CABasicAnimation を取り除いてしまうとタブがアニメーション前の形に戻ってしまいます。
アニメーション完了のコールバックの中で tabLayer.pathlabelLayer.path に値を代入して、実際の path の値を代入してあげる必要があります。

ではコールバックの設定方法ですが、 CATransaction を使って実装します。
CATransaction#setCompletionBlock の中に処理を書きます。
CATransactionUIView#animateWithDurationfinished のフラグが無いので、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に書いてみました。
この記事に載せたサンプルコードはほんの一部なので、実際のコードの方もよければ動かしてみてください。

16
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
17