CoreAnimationのfillModeについて

# はじめに
　CoreAnimationを使って少し凝ったアニメーション等を実装する時に、毎回おまじないのように`fillMode`を設定していたのですが、それについて少し調べたので書いていきたいと思います

# fillModeとは
　アニメーションの終了時の動作を決めるためのプロパティだと理解しています

## fillModeの種類と役割
- removed:
アニメーションが終了したら削除する。デフォルトでremovedが設定されています
- forwards:
アニメーションが終了時は最後の状態を保持する
- backwards:
アニメーションが終了時は最初の状態で固定する
- both:
forwardsとbothの両方を適用したもの

## 実際に動きを見てみる
### ベースとなるコード
`backgroundColor`を`green`にしただけの`UIView`があります。

```swift
let animatedView = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100)))
animatedView.backgroundColor = .green
```

`animatedView`をCoreAnimationを使って、`origin.x`を200に、`backgroundColor`を`blue`にアニメーションさせます

```swift
let translateAnimation = CABasicAnimation(keyPath: "transform.translation.x")
translateAnimation.beginTime = CACurrentMediaTime() + 0.5
translateAnimation.duration = 1.0
translateAnimation.fromValue = CATransform3DIdentity
translateAnimation.toValue = 200
translateAnimation.isRemovedOnCompletion = false

let backgroundAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.backgroundColor))
backgroundAnimation.beginTime = CACurrentMediaTime() + 0.5
backgroundAnimation.duration = 1.0
backgroundAnimation.fromValue = UIColor.green.cgColor
backgroundAnimation.toValue = UIColor.blue.cgColor
backgroundAnimation.isRemovedOnCompletion = false

animatedView.layer.add(backgroundAnimation, forKey: nil)
animatedView.layer.add(translateAnimation, forKey: nil)
```
※アニメーションがわかりやすくなるように`isRemovedOnCompletion`を`false`にしています

これらに`fillMode`を設定して挙動を違いを見ていきます。

### `.removed`の時
　`fillMode`のデフォルト値が`.removed`なので、先程のコードのままでも同じ挙動になります。

```diff
...
+translateAnimation.fillMode = .removed

...
+backgroundAnimation.fillMode = .removed
...
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98075/812ec878-f59b-e423-c1b7-6921d907c338.gif" height=200>

　アニメーションが終わった時、アニメーションが削除されて初期状態に戻っているのがわかります。

### `.forwards`の時
　両方の`fillMode`を`.forwards`に設定します。

```diff
...
+translateAnimation.fillMode = .forwards

...
+backgroundAnimation.fillMode = .forwards
...
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98075/fd15b540-4673-bfe8-1c76-071ccdebb35b.gif" height=200>

　アニメーションが終わった時、最後の状態を保持し続けているのがわかります。

### `.backwards`の時
　両方の`.fillMode`を`backwards`に設定します。

```diff
...
+translateAnimation.fillMode = .backwards

...
+backgroundAnimation.fillMode = .backwards
...
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98075/38ebe233-1a2c-ec36-788b-94f5b9d1b9b9.gif" height=200>

　`.removed`の時と同じ挙動になってしまいました。わかりづらいので、`translateAnimation`の`fillMode`だけ`.forwards`にして見たいと思います。

```diff
...
-translateAnimation.fillMode = .backwards
+translateAnimation.fillMode = .forward

...
+backgroundAnimation.fillMode = .backwards
...
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98075/853f08e1-b173-5420-9b63-90261f0516e6.gif" height=200>

最後の`background`の状態が`green`になっている通り、アニメーションが終わった時、最初の状態を保持しているのがわかります。

### `.both`の時
 両方の`fillMode`に`.both`を設定します。

```diff
...
+translateAnimation.fillMode = .both

...
+backgroundAnimation.fillMode = .both
...
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98075/17d45a86-9948-c34b-18e0-e86708bfb051.gif" height=200>

見かけでは `.forwards`と一緒ですが、連続で何回もアニメーションすると違いがわかります。

#### `.both`を連続でアニメーションさせた時

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98075/3e93c707-3dd9-f65b-81ea-9f10911e3a26.gif" height=200>

#### `.forward`を連続でアニメーションさせた時

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98075/923dbf54-fb20-1f4a-178d-e640bd48d06a.gif" height=200>

`.both`だといい感じになっていますね。

# 最後に
　基本的には、`isRemovedOnCompletion = false`と`.fillMode = .forwards`の組み合わせで使うことがほとんどだと思いますが、他にもこんなプロパティがあるよ、という紹介でした。

# 参考
- [fillMode - Apple Developer](https://developer.apple.com/documentation/quartzcore/camediatiming/1427656-fillmode)