WEARに投稿したコーディネート画像に雪を降らせて映えを狙う

##はじめに
この記事は、ZOZOテクノロジーズ [#4AdventCalendar2019](https://qiita.com/advent-calendar/2019/zozo_tech4)の記事です。
昨日は@kurararararaさんの[Charles for Macでレスポンスを変更する方法](https://qiita.com/kurarararara/items/abb4cabf0087e4f6e2e8)の記事でした。

ZOZOテクノロジーズでは、他にもAdventCalenderを書いている方がいらっしゃるので、よかったらみていってください。

- [ZOZOテクノロジーズ #1AdventCalendar2019](https://qiita.com/advent-calendar/2019/zozo_tech)
- [ZOZOテクノロジーズ #2AdventCalendar2019](https://qiita.com/advent-calendar/2019/zozo_tech2)
- [ZOZOテクノロジーズ #3AdventCalendar2019](https://qiita.com/advent-calendar/2019/zozo_tech3)
- [ZOZOテクノロジーズ #5AdventCalendar2019](https://qiita.com/advent-calendar/2019/zozo_tech5)


##映えを狙う
今回私@koiwai2020は、WEARに投稿したこちらのコーディネート画像に雪を降らせて、映えを狙いたいと考えました。
<img width="414" alt="ゆき.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368129/aa5c37e8-cd41-db0a-b776-bb4ab6fc156e.png">

今回は[CABasicAnimation](https://developer.apple.com/documentation/quartzcore/cabasicanimation)を使ったアニメーションによって、雪が降らせてみることに挑戦しました。

早速ですがアニメーションを使用して、投稿画像に雪を降らせた完成形がこちらになります。
<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/1N8pAwqitv">pic.twitter.com/1N8pAwqitv</a></p>&mdash; 小岩井 (@WfODXAd0jmop1Ev) <a href="https://twitter.com/WfODXAd0jmop1Ev/status/1206445895643881472?ref_src=twsrc%5Etfw">December 16, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



CABasicAnimationには```opacity```、```backgroundColor```、```position```、```transform.scale.x```などのkey pathが用意されていて、これを指定してアニメーションを作ります。
今回雪が降る様子を作る為に、以下の2つの動きをCABasicAnimationを使って表現しました。

- 雪の画像を移動させる。
- 雪の画像を徐々に薄くする。

##画像を移動させるアニメーション
雪を降らせるにあたって、まず画像を移動させるアニメーションを作ります。
移動させるアニメーションは、```position```をkey pathに設定して使います。

```swift:
func fallAnimation() -> CABasicAnimation {
        let fallAnimation = CABasicAnimation(keyPath: "position")
        // 0から1までのランダムな係数を用意して、アニメーションの位置を調整するための値
        let random = CGFloat.random(in: 0 ..< 1)
        // アニメーション開始時の座標
        let fromPoint = CGPoint(x: UIScreen.main.bounds.width * random, y: 0)
        // アニメーション完了時の座標
        let toPoint = CGPoint(x: UIScreen.main.bounds.width * random, y: UIScreen.main.bounds.height)
        // アニメーション開始時の座標を設定
        fallAnimation.fromValue = fromPoint
        // アニメーション完了時の座標を設定
        fallAnimation.toValue = toPoint
        // アニメーションの速度を設定
        fallAnimation.duration = 1.5

        return fallAnimation
}
```

##画像を透過させるアニメーション
次に降る雪が地上に近くにつれて、溶けていく様子を画像を透過させるアニメーションを使って作ります。
透過のアニメーションは、```opacity```をkey pathに設定して使います。

```swift:
func thawAnimation() -> CABasicAnimation {
        let thawAnimation = CABasicAnimation(keyPath: "opacity")
        // アニメーション開始時のalpha値
        thawAnimation.fromValue = 1.0
        // アニメーション終了時のalpha値
        thawAnimation.toValue = 0.0
        // アニメーションの速度を設定
        thawAnimation.duration = 1.5
        // アニメーション完了時の状態を保持
        thawAnimation.fillMode = .forwards

        return thawAnimation
}
```

##作ったアニメーションを画像に反映させる
作ったアニメーションを実際に、画像に反映させてみます。

アニメーションは、[CALayer](https://developer.apple.com/documentation/quartzcore/calayer)の```add(_:forKey:)```を呼び出したタイミングで開始されます。
その為、次の```startSnowAnimation```が呼び出されたタイミングで雪の画像が徐々に薄くなりながら、落ちていきます。

```swift:画像にアニメーションを追加する処理
func startSnowAnimation() {
        view.addSubview(snowImageView)
        snowImageView.layer.add(fallAnimation(), forKey: "")
        snowImageView.layer.add(thawAnimation(), forKey: "")
}
```

##アニメーションを繰り返す
実際に```startAnimation```を実行してみると、一粒の雪しか降ってきませんでした。
なので今回は、Timerを使用して一定の周期でアニメーションを開始するようにしました。


```swift:用意したTimer
let snowAnimationTimer = Timer.scheduledTimer(timeInterval: 0.2,
                                              target: self,
                                              selector: #selector(startSnowAnimation),
                                              userInfo: nil,
                                              repeats: true)
```
用意したTimerを、アニメーションを開始したい任意のタイミングで動かします。

```swift:Timerを動かす
override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        RunLoop.main.add(snowAnimationTimer, forMode: .common)
}
```
これで雪のアニメーションが繰り返し行われるので、自然な見た目になります。

##アニメーションが終ったら
アニメーションが終ったら、追加したアニメーションや画像の削除を行います。
アニメーションの完了には、[CAAnimationDelegate](https://developer.apple.com/documentation/quartzcore/caanimationdelegate)にて用意されている```animationDidStop:finished:```を使います。
```animationDidStop:finished:```を呼ぶ為には、最初に作ったアニメーションの設定処理に以下の設定を追加する必要があります。

- アニメーションにdelegateを設定する。
- ```isRemovedOnCompletion```プロパティを無効にする。

[isRemovedOnCompletion](https://developer.apple.com/documentation/quartzcore/caanimation/1412458-isremovedoncompletion)は、アニメーションが完了した際に削除するかどうかを決定します。
これが有効のままですと、```animationDidStop:finished:```は呼ばれずに、アニメーションのオブジェクトは削除されます。

透過させるアニメーション```thawAnimation```を例に設定を追加すると、次のようになります。

```swift:
func thawAnimation() -> CABasicAnimation {
        let thawAnimation = CABasicAnimation(keyPath: "opacity")

        thawAnimation.delegate = self // CAAnimationDelegateの設定を追加

        thawAnimation.fromValue = 1.0
        thawAnimation.toValue = 0.0
        thawAnimation.duration = 1.5
        thawAnimation.fillMode = .forwards

        thawAnimation.isRemovedOnCompletion = false //isRemovedOnCompletionはデフォルトtrue

        return thawAnimation
}
```
設定が済んだら、```animationDidStop:finished:```内にアニメーション完了時の処理を追加します。
今回は、アニメーションが完了したタイミングで雪画像のImageViewごと削除しています。

```swift:
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        snowImageView.removeFromSuperview()
}
```

以上で、投稿したコーディネート画像を映えさせる?雪のアニメーションの実装ができました。

##さいごに
CABasicAnimationの```fromValue```や```toValue```、```duration```を調整することでアニメーションの印象が変わりました。
普段慣れ親しんでいる雪であっても、実際に表現するとなると難しいのだなと感じました。

明日は、@Kyou13さんです。

読んで下さった皆さま、ありがとうございました🙇‍♂️🏂🙇‍♂️