[SwiftUI] timingCurveを用いた緩急のあるアニメーション例と解説

以前、このような物理法則っぽいLoadingのアニメーションを作成しました。

![Screen Recording 2023-07-20 at 11.38.47.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3352230/d873750e-70f6-562b-1560-5b00f1628233.gif)

これでは、AnimationにtimingCurveを使って、アニメーションに緩急をつけています。



## timingCurveのドキュメント

https://developer.apple.com/documentation/swiftui/animation/timingcurve(_:_:_:_:duration:)

# 解説

まず、もっと簡単な場合を考えてみましょう。

```swift
struct ProgressCircleExplain: View {
    @State private var animate = false
    
    var body: some View {
        ZStack {
            Circle() // Outer circle
                .frame(width: 250, height: 250)
                .foregroundColor(.gray)
                .opacity(0)
            
            Circle() // Single Moving circle
                .frame(width: 20, height: 20)
                .foregroundColor(Color.blue)
                .offset(y: 50) // Set the radius of the outer circle here
                .rotationEffect(.degrees(self.animate ? 360 : 0))
                .animation(
                    Animation
                        .timingCurve(0.1, 0.7, 0.9, 0.2, duration: 1.9)
                        .repeatForever(autoreverses: false)
                )
            .onAppear() {
                self.animate = true
            }
        }
    }
}
```

これは、一個の円が円を描いて開店するアニメーションのコードです。


![Screen Recording 2023-08-06 at 19.55.55.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3352230/4a71e876-fefe-38b0-15b2-5357181750d2.gif)

等速で円周上を回転していますね。

これに、緩急をつけることで滑り落ちるようなアニメーションにします。

timingCurve はアニメーションに割り当てられる時間を、調整することができます。

実際に先ほどのコードにtimingCurveを適応したものを実装してみましょう。

```swift 
struct ProgressCircleExplain: View {
    @State private var animate = false
    
    var body: some View {
        ZStack {
            Circle() // Outer circle
                .frame(width: 250, height: 250)
                .foregroundColor(.gray)
                .opacity(0)
            
            Circle() // Single Moving circle
                .frame(width: 20, height: 20)
                .foregroundColor(Color.blue)
                .offset(y: 50) // Set the radius of the outer circle here
                .rotationEffect(.degrees(self.animate ? 360 : 0))
                .animation(
                    Animation
                        .timingCurve(0.1, 0.7, 0.9, 0.2, duration: 1.9)
                        .repeatForever(autoreverses: false)
                )
            .onAppear() {
                self.animate = true
            }
        }
    }
}

```

これを動かすと以下のようになります。


![Screen Recording 2023-08-06 at 20.00.54.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3352230/25bdb5fc-c5a6-efc1-dbba-a994a1fc870d.gif)

今回のアニメーションは以下のように図示することができます。

![Frame 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3352230/443dd8cf-6109-192a-776a-0d34edbd3337.png)


今回のアニメーションの円運動は、0~360°まで変異させることで、円周上を移動するアニメーションを実装しています。

滑り落ちる挙動にしたければ、降っている状態では、時間の進みが早く、上りの時に遅くなるようにすればそれに近づけれます。

そこで使用するのがtimingCurveです。

timingCurveはアニメーションを分割し、それらにかかる時間を調整することが可能です。

今回は

```swift
.timingCurve(0.1, 0.7, 0.9, 0.2, duration: 1.9)
```

を使用しています。これは
0~90°は0.1秒で処理をする。
90~180°は0.7秒で処理をする。
180~270°は0.9秒で処理をする。
270~360°は0.2秒で処理をする。

という表現になります。

![Frame 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3352230/33125205-dfe4-0965-0d53-b30c5f5aa1a0.png)

図解するとこのようになります。

時間の総和はDurationと一致するようにしないと、アニメーションを行うたびにずれてしまうため、注意しましょう。


# まとめ
このような自然法則っぽいアニメーションは実装が難しそうですが、timingCurveを使うことで簡単に再現することができました。

これでおしゃれなアニメーションを作ってみてくださいね

いいね、ブックマーク、フォローしていただけると勉強の励みになりますので是非お願いします。😉

追伸　--

Twitterで日々の学習風景を投稿してます。

@Ren_yello