iOSアプリ開発でアニメーションするなら押さえておきたい基礎

アニメーション処理はiOSアプリを開発する醍醐味の1つであると私は思っています

なので、本記事ではiOSアプリ開発をする上で押さえておきたいアニメーションの基礎の部分をお伝えできればと思います

# UIKitアニメーション

## UIView.animateメソッド

iOS開発をする人にはお馴染みのUIViewのクラスメソッドである

```swift
open class func animate(withDuration duration: TimeInterval, delay: TimeInterval, options: UIViewAnimationOptions = [], animations: @escaping () -> Swift.Void, completion: ((Bool) -> Swift.Void)? = nil)
```

を使うことで簡単にアニメーションを実現することができます

`duration`はアニメーション時間
`delay`は開始までの遅延時間
`options`ではアニメーション中に使用するタイミング曲線の種類やアニメーションの逆再生などを指定できます
`animations`クロージャの中でアニメーションしたいUIViewクラスのプロパティの値を変更します
`completion`クロージャはアニメーションが完了したタイミングで呼ばれるクロージャです


青いViewを1.0秒で下方向に100ポイント移動させる

```swift

self.blueView.center = self.view.center
UIView.animate(withDuration: 1.0, delay: 0.0, options: .autoreverse, animations: { 
    self.blueView.center.y += 100.0
}, completion: nil)
```

![movie1.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/0506b7ca-3c1d-a385-5a2b-38e1f870532e.gif)

`oprions`に`autoreverse`を追加することでアニメーションの逆再生が行われます

```swift

UIView.animate(withDuration: 1.0, delay: 0.0, options: [.curveEaseIn, .autoreverse], animations: { 
    self.blueView.center.y += 100.0
}, completion: nil)

```

![movie2.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/25da85f4-b072-c7eb-0d00-8cc24a04eee5.gif)

`.autoreverse`でアニメーションを逆再生しても、アニメーションが終わるとViewの位置は`animations`クロージャで変更した値になってしまうので、アニメーション完了の`completion`クロージャにViewの変更値を逆再生と同じ位置にしてあげることで自然になります

```swift
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.curveEaseIn, .autoreverse], animations: {
    self.blueView.center.y += 100.0
}) { _ in
    self.blueView.center.y -= 100.0
}
```
![movie3.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/60c1fc64-d2f7-655c-2a83-ecd3bd39c894.gif)


UIViewのalpha値を変更するアニメーションを使うと、ふわっとViewが出現するので本当に簡単に良い感じのアニメーションが実現できます

```swift

blueView.alpha = 0.0
UIView.animate(withDuration: 2.0, delay: 1.0, options: [.curveEaseIn], animations: {
    self.blueView.alpha = 1.0
}, completion: nil)

```
![movie4.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/3b1e1f70-8539-3ad2-bd9a-87b4f92d574b.gif)


`options`に`repeat`を指定してあげるとアニメーションを永遠に繰り返します
チュートリアル等で「ここをスワイプすると〇〇できるよ！」みたいなことする時に指アイコンのアニメーションで使ったりします

```swift
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.repeat], animations: { 
    iconFingerImageView.frame.origin.x -= 50.0
}, completion: nil)
```

![movie5.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/bce7de39-6cba-b35f-318a-865fe8976954.gif)

UIView.animationメソッドで、ばねの跳ね返りみたいなアニメーションをしたければ

```swift
@available(iOS 7.0, *)
open class func animate(withDuration duration: TimeInterval, delay: TimeInterval, usingSpringWithDamping dampingRatio: CGFloat, initialSpringVelocity velocity: CGFloat, options: UIViewAnimationOptions = [], animations: @escaping () -> Swift.Void, completion: ((Bool) -> Swift.Void)? = nil)
```

を使うと実現できます

`dampingRatio`は振幅の大きさを指定します。1.0が最大で、この値が小さいほど振幅が大きくなります。1.0の時は、ばね効果が得られません

`velocity`はアニメーションの初速を変更できます

```swift

UIView.animate(withDuration: 1.0, delay: 0.0, usingSpringWithDamping: 0.1, initialSpringVelocity: 0.0, options: .autoreverse, animations: {
    self.blueView.center.y += 100.0
    self.blueView.bounds.size.height += 30.0
    self.blueView.bounds.size.width += 30.0
}) { _ in
    self.blueView.center.y -= 100.0
    self.blueView.bounds.size.height -= 30.0
    self.blueView.bounds.size.width -= 30.0
}

```

![movie6.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/84159d5e-6aaa-37dd-fa74-1b2c0c7cce4f.gif)


## UIView.transitionで遷移メソッド

こちらもUIViewのクラスメソッドである

```swift
open class func transition(with view: UIView, duration: TimeInterval, options: UIViewAnimationOptions = [], animations: (() -> Swift.Void)?, completion: ((Bool) -> Swift.Void)? = nil)
```

を使うことで簡単に遷移アニメーションを実現できます

引数の`view:`に遷移アニメーションの視覚効果の対象となるViewを指定します
`options:`には、先ほどの`UIView.animate`で使っていたもの以外にも`transitionFlipFromLeft`,`transitionFlipFromRight`,`transitionCurlUp`,`transitionCurlDown`,`transitionCrossDissolve`,`transitionFlipFromTop`,`transitionFlipFromBottom`といった視覚効果を得られるOptionが使えます


##### UIViewAnimationOptions.transitionFlipFromLeft

```swift
UIView.transition(with: blueView, duration: 1.0, options: [.transitionFlipFromLeft], animations: nil, completion: nil)
```

![movie7.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/62e7a0c8-4456-f3ab-872b-50078acc42ff.gif)

##### UIViewAnimationOptions.transitionFlipFromRight

```swift
UIView.transition(with: blueView, duration: 1.0, options: [.transitionFlipFromRight], animations: nil, completion: nil)
```

![movie8.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/01d0c8f0-7c1c-11e8-789c-930d47b95def.gif)

##### UIViewAnimationOptions.transitionFlipFromTop

```swift
UIView.transition(with: blueView, duration: 1.0, options: [.transitionFlipFromTop], animations: nil, completion: nil)
```

![movie9.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/e9f81897-02a7-9d97-d181-c632745be4f6.gif)


##### UIViewAnimationOptions.transitionFlipFromBottom

```swift
UIView.transition(with: blueView, duration: 1.0, options: [.transitionFlipFromBottom], animations: nil, completion: nil)
```

![movie10.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/99c4ea2b-26e2-5632-093f-a375b47ea408.gif)

##### UIViewAnimationOptions.transitionCurlUp

```swift
UIView.transition(with: blueView, duration: 1.0, options: [.transitionCurlUp, .autoreverse], animations: {
    self.blueView.isHidden = true
}) { _ in
    self.blueView.isHidden = false
}
```

![movie11.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/6102fb8a-01b4-4105-480a-d861a7cbe117.gif)

##### UIViewAnimationOptions.transitionCurlDown

```swift
UIView.transition(with: blueView, duration: 1.0, options: [.transitionCurlDown, .autoreverse], animations: nil, completion: nil)
```

![movie12.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/ff53def0-dec3-3fe3-ace2-e3d0043336e6.gif)

##### UIViewAnimationOptions.transitionCrossDissolve

```swift
UIView.transition(with: blueView, duration: 1.0, options: [.transitionCrossDissolve, .autoreverse], animations: {
    self.blueView.isHidden = true
}) { _ in
    self.blueView.isHidden = false
}
```

![movie13.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/8c8158fd-5386-8810-d885-dc6d714d1c36.gif)

また、遷移アニメーションのメソッドは、もう１つ

```
open class func transition(from fromView: UIView, to toView: UIView, duration: TimeInterval, options: UIViewAnimationOptions = [], completion: ((Bool) -> Swift.Void)? = nil)
```

というものもあります

`fromView`に指定したViewのSuperViewが遷移アニメーション対象になり、アニメーションで`fromView`が`removeFromSuperView`され`toView`が`fromView`のSuperViewに`addSubView`される挙動になります

```swift
let redView = UIView(frame: blueView.frame)
redView.backgroundColor = .red
UIView.transition(from: blueView, to: redView, duration: 1.0, options: [.transitionCurlDown, .autoreverse]) { _ in
    self.view.addSubview(self.blueView)
    redView.removeFromSuperview()
}
```

![movie14.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/fb027fd5-b05a-6527-e72d-7c9d1f879a6f.gif)

## UIView.animateKeyframesメソッド

キーフレームアニメーションをしたい場合は

```swift
open class func animateKeyframes(withDuration duration: TimeInterval, delay: TimeInterval, options: UIViewKeyframeAnimationOptions = [], animations: @escaping () -> Swift.Void, completion: ((Bool) -> Swift.Void)? = nil)
```

と

```swift
open class func addKeyframe(withRelativeStartTime frameStartTime: Double, relativeDuration frameDuration: Double, animations: @escaping () -> Swift.Void)
```

を使います

あるアニメーションの処理が終わったら次のアニメーションというように連鎖アニメーションをしたい場合、今までに紹介したアニメーション方法だと、アニメーション終了時の`completion`クロージャの中で再度アニメーションの処理をネストして書くなどしなければならず可読性の悪いコードになってしまいますが`UIView.animateKeyframes`を使うと下記のように書くことができます


```swift
UIView.animateKeyframes(withDuration: 2.0, delay: 0.0, options: [.autoreverse], animations: {
            
    UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: {
        self.blueView.center.y += 100.0
    })
            
    UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.1, animations: {
        self.blueView.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2))
    })
            
    UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25, animations: {
        self.blueView.center.x += 100.0
    })
            
    UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.1, animations: {
        self.blueView.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI))
    })
            
    UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.25, animations: {
        self.blueView.center.y -= 100.0
    })
            
    UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.1, animations: {
        self.blueView.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI + M_PI_2))
    })
            
    UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25, animations: {
        self.blueView.center.x -= 100.0
    })
            
    UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.1, animations: {
        self.blueView.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI * 2.0))
    })
            
}, completion: nil)
```

![movie15.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/dcdff6d5-02c8-0c6c-e563-4817bf93d53d.gif)

`UIView.animateKeyframes`メソッドの`animations`クロージャに`UIView.addKeyframe`を追加していくことで可能です
`UIView.animateKeyframes`の`withDuration:`に指定した値がキーフレームアニメーション全体の時間になります

`UIView.addKeyframe`メソッドの`withRelativeStartTime:`では、全体のアニメーション時間の中のどこからアニメーションを開始するかを0.0〜1.0の間で指定します
`relativeDuration:`でアニメーション時間を指定します


なので、下記のコードでは

```swift
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25, animations: {
    self.blueView.center.x += 100.0
})
```

2.0(全体アニメーション時間) * 0.25(withRelativeStartTime) = (アニメーション開始から)0.5秒後、0.25秒でViewのCenterPositionのx座標を100point移動するという意味です

また、サンプルコードでは移動だけでなく、

```swift
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.1, animations: {
    self.blueView.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI))
})
```

2.0(全体アニメーション時間) * 0.25(withRelativeStartTime) = (アニメーション開始から)0.5秒後、0.1秒でViewを90度回転させるというコードも書いてあるのが分かる通り、全体のアニメーション時間内の任意のタイミング、時間にアニメーションすることができます


# Core Animation
複雑なアニメーションやUIKitでサポートされてないアニメーションをしたい場合はCore Animationを使ってレイヤーに変更を与えることでアニメーションができるようになります

例えば、`UIView.animate`メソッドの`animations:`クロージャでUIViewのレイヤーのプロパティを変更して、アニメーションできるものもありますが、CALayerのcornerRadiusのアニメーションの場合、下記の通り、UIKitのアニメーションではうまいこと動いてくれません

```swift
//cornerradiusはUIKitのアニメーション非対応
//2秒でcornerRadiusが20になるようにアニメーションしたいけどパッと切り替わったようになってしまう
UIView.animate(withDuration: 2.0, animations: { in
    self.blueView.layer.cornerRadius = 20.0
}, completion: nil)
```

![movie16.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/ade6d4ff-ac11-67d0-c688-b215bfed226a.gif)

この現象を解決したい場合にCore Animationを使います

## CABasicAnimation

ここではCore AnimationのCABasicAnimationというクラスを使います

```swift
let animation = CABasicAnimation(keyPath: "cornerRadius")
animation.duration = 2.0
animation.fromValue = 0.0
animation.toValue = 20.0
animation.autoreverses = false
animation.isRemovedOnCompletion = false
animation.fillMode = kCAFillModeForwards
blueView.layer.add(animation, forKey: nil)
```

![movie17.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/8c77bd98-ac80-c3c0-09b0-e2e9ce3b08dc.gif)

CABasicAnimationのイニシャライザの引数である`keyPath:`にアニメーション対象のレイヤーのプロパティを文字列で指定します
(正確にいうとCABasicAnimationの親クラスであるCAPropertyAnimationクラスのイニシャライザです)

今回は`cornerRadius`ですが、x軸の移動の場合は`position.x`と指定します

あとはCABasicAnimationクラスのプロパティに値を設定していきます
(親クラスであるCAPropertyAnimation,CAAnimationのプロパティやデリゲート含め)

`duration`はアニメーション時間

`autoreverses`は逆再生の可否

`fromValue`はイニシャライザで指定したアニメーション対象のプロパティのアニメーション前の値をセットします
指定しない場合は現状の値がそのまま使われます

`toValue`はイニシャライザで指定したアニメーション対象のプロパティのアニメーション後の値をセットします

`isRemovedOnCompletion`と`fillMode`についてですが、Core Animationの場合、UIKitのアニメーションと逆で、アニメーション後はアニメーションした位置や値に留まるのではなく、アニメーション前の値に戻ります
なのでアニメーション後に、その位置、値に留めておきたい場合、`isRemovedOnCompletion`をfalse、`fillMode`に`kCAFillModeForwards`を指定する必要があります
このあたりの解説は @inamiy さんの [Core Animation 中級編](http://qiita.com/inamiy/items/bdc0eb403852178c4ea7#fillmode%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)にとても分かりやすく説明されています

作成したアニメーションオブジェクトをアニメーション対象のViewのレイヤーに`add`することでアニメーションが実行されます

また、UIBezierPathの描画アニメーションの際にもCore Animationを使います
`UIBezierPath`や`CAShapeLayer`には触れずなので恐縮ですが、下記の通り実現できます

```swift
let path = UIBezierPath()
path.move(to: CGPoint(x: view.frame.maxX, y: view.frame.minY))
path.addLine(to: CGPoint(x: view.frame.minX, y: view.frame.maxY))
path.lineWidth = 1.0
let lineLayer = CAShapeLayer()
lineLayer.strokeColor = UIColor.blue.cgColor
lineLayer.lineWidth = 1.0
lineLayer.path = path.cgPath

let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = 1.0
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
animation.fromValue = 0.0
animation.toValue = 1.0
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false

view.layer.addSublayer(lineLayer)
lineLayer.add(animation, forKey: nil)
```

![movie18.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/b1de3e08-0d51-36fc-b3ae-6c8b133d7956.gif)

また、`timingFunction`プロパティにタイミング曲線の種類を指定できます

他に、UIKitのアニメーションでもrotationのアニメーションは`view.layer.transform`の変更で可能ですが、Core Animationを使って下記のようにやるのが個人的には好きです

```swift
@IBAction func tappedRightBarButtonItem(_ sender: Any) {
        
    guard let item = sender as? UIBarButtonItem, let target = item.value(forKey: "view") as? UIView else {
        return
    }
        
    let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
    rotateAnimation.toValue = M_PI_4
    rotateAnimation.duration = 1.0
    rotateAnimation.isRemovedOnCompletion = false
    rotateAnimation.fillMode = kCAFillModeForwards
    target.layer.add(rotateAnimation, forKey: nil)

}
```

![movie24.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/e0d6189b-16fa-189c-d83a-81bc7f14a296.gif)


#### Core Animationの遅延実行

遅延実行をしたい場合は`beginTime`プロパティを使います
下記は0.5秒遅れてアニメーションが実行されます

```swift
let animation = CABasicAnimation(keyPath: "cornerRadius")
animation.beginTime = CACurrentMediaTime() + 0.5
```

#### Core Animationの停止

アニメーションの停止を行いたい場合は`speed`プロパティに0を指定してあげることで可能です
デフォルトは1で、仮にこの値を2.0などにした場合、2倍速のアニメーションになります、すなわちアニメーション時間`duration`に1.0を指定して、`speed`を2.0にした場合、アイメーションは0.5秒で終わる挙動になります。
なので、`speed`が0の場合は動かないということです

```swift
let animation = CABasicAnimation(keyPath: "cornerRadius")
//アニメーションが開始されない
animation.speed = 0.0
```

```swift
let animation = CABasicAnimation(keyPath: "cornerRadius")
animation.duration = 1.0
//2倍速になるのでアニメーションが0.5秒で完了する
animation.speed = 2.0
```

#### Core Animationの開始・終了の検知

開始・終了を検知したい場合は、CAAnimationDelegateプロトコルに準拠することで可能です

開始を検知したい場合は`optional public func animationDidStart(_ anim: CAAnimation)`
終了を検知したい場合は`optional public func animationDidStop(_ anim: CAAnimation, finished flag: Bool)`

下記はアニメーション終了時にViewを黒くして、登録されたアニメーションを削除しています

```swift
//アニメーション側
let animation = CABasicAnimation(keyPath: "cornerRadius")
animation.duration = 2.0
animation.autoreverses = false
animation.fromValue = 0.0
animation.toValue = 20.0
animation.isRemovedOnCompletion = false
animation.fillMode = kCAFillModeForwards
animation.delegate = self
animation.setValue(blueView.layer, forKey: "blueView.layer")
blueView.layer.add(animation, forKey: "cornerRadius")
```

```swift
extension ViewController: CAAnimationDelegate {
    
    func animationDidStart(_ anim: CAAnimation) {
        //開始時
    }
    
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        
        if flag, let layer = anim.value(forKey: "blueView.layer") as? CALayer {
            layer.backgroundColor = UIColor.black.cgColor
            layer.removeAnimation(forKey: "cornerRadius")
        }
        
    }
    
}
```

ここでは、まず、アニメーション登録時にデリゲートをセットします

```swift
animation.delegate = self
```

また、アニメーションに対してアニメーション対象のレイヤーオブジェクトを"blueView.layer"という文字列で登録します

```swift
animation.setValue(blueView.layer, forKey: "blueView.layer")
```

そしてアニメーションの名前も登録しておきます

```swift
blueView.layer.add(animation, forKey: "cornerRadius")
```

デリゲートに準拠した側では、"blueView.layer"キーから該当のレイヤーを取得して色を変えたり、レイヤーのアニメーションを削除しています

![movie19.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/cf5bd89a-a684-27aa-37d3-680d3a7fe9fb.gif)


## CAAnimationGroup

1つのLayerに対して複数のアニメーション処理を適用したい場合はCAAnimationGroupクラスを使います
CAAnimationGroupクラスの`open var animations: [CAAnimation]?`に各々のCAAnimation(CABasicAnimationなど)クラスを登録することができます

CAAnimationGroupクラスのインスタンスで`duration`などを設定すると、`animations`に指定した各々のCAAnimationクラスで設定した値よりも優先されます
全体に共通の設定はCAAnimationGroupのプロパティに設定すると、各々のAnimationクラスでは書かなくて良くなります


```swift

let animationGroup = CAAnimationGroup()
animationGroup.duration = 1.0
animationGroup.fillMode = kCAFillModeForwards
animationGroup.isRemovedOnCompletion = false

let animation1 = CABasicAnimation(keyPath: "transform.scale")
animation1.fromValue = 2.0
animation1.toValue = 1.0

let animation2 = CABasicAnimation(keyPath: "cornerRadius")
animation2.fromValue = 0.0
animation2.toValue = 20.0

let animation3 = CABasicAnimation(keyPath: "transform.rotation")
animation3.fromValue = 0.0
animation3.toValue = M_PI * 2.0
animation3.speed = 2.0

animationGroup.animations = [animation1, animation2, animation3]
blueView.layer.add(animationGroup, forKey: nil)

```

![movie20.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/bfbb17ab-e339-9bcd-b19e-9b43d1aab953.gif)


グループの中のあるアニメーション処理だけ時間を早くしたい場合には、そのAnimationオブジェクトの`speed`プロパティの値を変えてあげることで実現できます


## CASpringAnimation
Core AnimationでもiOS9からCASpringAnimationを使えば、ばねの跳ね返りアニメーションをすることができます
UIKitよりも詳細な設定が可能です


```swift

let animation = CASpringAnimation(keyPath: "transform.scale")
animation.duration = 2.0
animation.fromValue = 1.25
animation.toValue = 1.0
animation.mass = 1.0
animation.initialVelocity = 30.0
animation.damping = 3.0
animation.stiffness = 120.0
blueView.layer.add(animation, forKey: nil)

```

UIKitで使っていた

```swift
open class func animate(withDuration duration: TimeInterval, delay: TimeInterval, usingSpringWithDamping dampingRatio: CGFloat, initialSpringVelocity velocity: CGFloat, options: UIViewAnimationOptions = [], animations: @escaping () -> Swift.Void, completion: ((Bool) -> Swift.Void)? = nil)
```

メソッドの設定だけでなく、
`mass` 質量
と
`stiffness` 硬さ

を指定できるようになっています

![movie21.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/a6bbd138-f7e4-595f-287b-05ae54972757.gif)


## CAKeyframeAnimation

Core Animationでキーフレームアニメーションをしたい場合はCAKeyframeAnimationを使います
UIKitのキーフレームアニメーションは`UIView.animateKeyframes`の`animations:`クロージャの中でアニメーションしたい分の`UIView.addKeyframe`を追加していましたが、CAKeyframeAnimationでは`keyTimes`や`values`に、タイミングや値を配列で書けるのでUIKitより冗長でなくなります


```swift
let animation = CAKeyframeAnimation(keyPath: "cornerRadius")
animation.duration = 2.0
animation.keyTimes = [0.0, 0.25, 0.5, 0.75]
animation.values = [20.0, 3.0, 20.0, 10.0]
blueView.layer.add(anim, forKey: nil)
```

![movie22.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/9b0b3077-bc53-a743-f5db-008ffddcb290.gif)

## CATransaction

CoreAnimationの終了の検知はCAAnimationDelegateプロトコルに準拠することで可能ですが、CATransactionの`setCompletionBlock`を使うと実装者がCAAnimationDelegateに準拠せずに終了の検知ができます

```swift
CATransaction.begin()
CATransaction.setCompletionBlock {
    self.blueView.backgroundColor = UIColor.red
}
let animation = CABasicAnimation(keyPath: "cornerRadius")
animation.duration = 1.0
animation.fromValue = 0.0
animation.toValue = 20.0
blueView.layer.add(animation, forKey: nil)
CATransaction.commit()
```

`CATransaction.begin()`と`CATransaction.commit()`の間にアニメーションの処理を書きますが、完了を検知する`setCompletionBlock`はアニメーションが開始される`blueView.layer.add(animation, forKey: nil)`よりも前の行に書いておく必要があります

![movie23.gif](https://qiita-image-store.s3.amazonaws.com/0/27785/de41f15d-e85f-87bb-b469-6620b4b7bd71.gif)

# インタラクティブアニメーション
ユーザーの操作に連動してアニメーションさせたい場合はUIViewPropertyAnimatorをつかうことで実現できます。
[UIViewPropertyAnimatorを使いこなそう](https://qiita.com/hachinobu/items/bc2e1fa0c3bbd0a51d17) で詳しく説明しています。

# 最後に

この記事では基礎的な部分だけでしたが、アイデアや組み合わせ次第で本当に素敵なものになりますよね

しかし、過剰なアニメーションは嫌われるし、複雑すぎるアニメーション処理は、その後のメンテナンス性の低下や冪等性を保つのが大変になる可能性があるので用法用量お守りください

アニメーションはViewに魂を与えます

色々試行錯誤してみて自分だけの良い感じのアニメーションを生み出してみてはいかがでしょうか

この記事がその助けになれれば幸いです