CoreAnimationとUIBezierPathを使用して円が拡大するアニメーションを実装する

2018.03.07

はじめに

こんばんは。モバイルアプリサービス部の平屋です。

本記事では、CoreAnimationUIBezierPathを使用して円が拡大するアニメーションを実装する方法を紹介します。

以下のスクリーンショットが実装結果です。

検証環境

本記事は以下の環境で検証を行っています。

  • macOS Sierra 10.13.2
  • Xcode Version 9.2 (9C40b)

実装

さっそく実装を紹介していきます。

CAShapeLayerを作成

アニメーション描画用のCAShapeLayerを作成し、ビューのレイヤーに追加します。

class ExpandingCircleView: UIView {

    var circleLayer: CAShapeLayer!

    override func awakeFromNib() {
        super.awakeFromNib()

        circleLayer = CAShapeLayer()
        circleLayer.frame = bounds
        circleLayer.fillColor = UIColor.black.withAlphaComponent(0.2).cgColor
        circleLayer.needsDisplayOnBoundsChange = true
        layer.addSublayer(circleLayer)
    }

    func animate() {
        // ...
    }
}

アニメーションの作成と適用

(1) アニメーション開始/終了時のパスを作成

アニメーション開始時のパスとして、半径100ptの円形のパスを作成し、アニメーションの開始値としてレイヤーに追加します。

また、終了時のパスとして、幅/高さがビューと等しい円形のパスを作成します。

func animate() {
    let startSize = CGSize(width: 100, height: 100)
    let inset = (bounds.width / 2) - (startSize.width / 2)
    let startRect = bounds.insetBy(dx: inset, dy: inset)
    let startPath = UIBezierPath(ovalIn: startRect).cgPath
    circleLayer.path = startPath

    let endPath = UIBezierPath(ovalIn: bounds).cgPath

    // ...
}

(2) パスを変化させるアニメーションを作成

アニメーション対象が「パス」のアニメーションを作成し、終了値/デュレーションなどを設定します。

func animate() {
    // ...

    let pathAnimation = CABasicAnimation(keyPath: "path") // パスを指定
    pathAnimation.toValue = endPath // 終了値
    pathAnimation.duration = 2 // アニメーション時間
    pathAnimation.fillMode = kCAFillModeForwards // アニメーション終了地点にフレームを残す
    pathAnimation.isRemovedOnCompletion = false // アニメーション終了後にフレームを残す

    // ...
}

(3) フェードアウトさせるアニメーションを作成

アニメーション対象が「不透明度」のアニメーションを作成し、開始値/終了値/デュレーションなどを設定します。

func animate() {
    // ...

    let fadeOutAnimation = CABasicAnimation(keyPath: "opacity") // 不透明度を指定
    fadeOutAnimation.fromValue = 1 // 開始値
    fadeOutAnimation.toValue   = 0 // 終了値
    fadeOutAnimation.duration  = 1 // アニメーション時間
    fadeOutAnimation.beginTime = 2 // 開始時間

    // ...
}

(4) 2つのアニメーションをグルーピング

(2)と(3)で作成したアニメーションをグルーピングし、デュレーション/繰り返し回数などを設定します。

そして、グループをレイヤーに追加します。

func animate() {
    // ...

    let animationGroup = CAAnimationGroup()
    animationGroup.duration = pathAnimation.duration + fadeOutAnimation.duration  // アニメーション時間
    animationGroup.repeatCount = .infinity // リピート回数:無限
    animationGroup.animations = [pathAnimation, fadeOutAnimation]

    circleLayer.add(animationGroup,
                    forKey: nil)
}

さいごに

本記事では、CoreAnimationとUIBezierPathを使用して円が拡大するアニメーションを実装する方法を紹介しました。

サンプルコードは以下のリポジトリで公開してます。

参考資料

関連記事

[iOS] カウントダウンビューを作ってみました