SwiftUIのAnimationについてふわっと学ぶ(animatableData編)

# はじめに
SwiftUIのアニメーションを実装するときに時々見かける`animatableData`
何のことかさっぱりだったので、キャッチアップしてみました。

# animatableDataとは
早速`animatableData`とはなんなのか、`animatableData`でググって一番最初に出てきた下記の記事を見てみました。

https://qiita.com/takehito-koshimizu/items/786dac0741e19552907b

`animatableData`は`Animatable`プロトコルが持つプロパティのようで、`animatableData`に設定した値が変わることでその値に紐づくコンポーネントがアニメーションできるようになるみたいです。

とは言っても文面だけでみても理解しきれなかったので、サンプルコードを書いてみます。

# やってみる
まずはanimatableDataを定義せずにアニメーションするのかを確認しました。
四角形のカスタムShapeをタップすると横幅と縦幅を変えるようにしました。
`animatableData`で`width`、`height`を設定していないので、おそらくこれらの値を更新してもアニメーションしないはず！

```swift
struct CustomRectangleView: View {
    
    @State private var width: CGFloat = 100
    @State private var height: CGFloat = 100
    
    var body: some View {
        ZStack {
            CustomShape(width: width, height: height)
        }
        .frame(maxWidth: width, maxHeight: height)
        .onTapGesture {
            withAnimation(.linear(duration: 0.5)) {
                width = CGFloat.random(in: 10...200)
                height = CGFloat.random(in: 10...200)
            }
        }
    }
    
    struct CustomShape: Shape {
        
        var width: CGFloat
        var height: CGFloat
        
        nonisolated func path(in rect: CGRect) -> Path {
            var path = Path()
            path.addRect(.init(x: (rect.width - width) / 2, y: (rect.height - height) / 2, width: width, height: height))
            return path
        }
    }
}
```

![NoAnimatableData.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/5d06b118-fd7e-fbfe-9465-220a1027d7db.gif)

こちらは想定通りアニメーションせずでした。

続いて、`animatableData`にwidthを設定してみます。

```swift
struct CustomRectangleView: View {
    
    @State private var width: CGFloat = 100
    @State private var height: CGFloat = 100
    
    var body: some View {
        ZStack {
            CustomShape(width: width, height: height)
        }
        .frame(maxWidth: width, maxHeight: height)
        .onTapGesture {
            withAnimation(.linear(duration: 0.5)) {
                width = CGFloat.random(in: 10...200)
                height = CGFloat.random(in: 10...200)
            }
        }
    }
    
    struct CustomShape: Shape {
        
        var width: CGFloat
        var height: CGFloat

        var animatableData: CGFloat { // 追加行
            　　　　　　　　　　　　　　　　// 追加行
            get { return width }　　　　// 追加行
            set { width = newValue }　// 追加行
        }                            // 追加行
        
        nonisolated func path(in rect: CGRect) -> Path {
            var path = Path()
            path.addRect(.init(x: (rect.width - width) / 2, y: (rect.height - height) / 2, width: width, height: height))
            return path
        }
    }
}
```

![WidthAnimatable.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/bd87bd61-d595-ad10-ef47-fa4d5b5991b9.gif)

上記例では、横幅の変更だけアニメーションし、縦幅はアニメーションしないようになっています。
ここまでの例で`animatableData`に設定したデータの変更がアニメーションするようになることがわかりました。

ただ調べてみるとそれだけではなさそうです。
`animatableData`の設定の仕方によってはアニメーションの仕方も変えられるみたいです。


```swift
struct CountUpAnimationTextView: View {
    
    @State private var text: String = "0"
    
    var body: some View {
        CountUpAnimationText(text: text)
            .onAppear {
                withAnimation(.linear(duration: 5)) {
                    text = "100"
                }
            }
    }
}

struct CountUpAnimationText: View, Animatable {
    
    private var text = "0"
    
    init(text: String = "0") {
        
        self.text = text
    }

    // 設定されたテキストをDouble型として参照する
    var animatableData: Double {
        
        get { Double(text) ?? 0 }
        set { text = String(format: "%.0f", newValue) }
    }
    
    var body: some View {
        Text(text)
            .font(.system(size: 30, weight: .bold))
    }
}

```

![TextAnimatable.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/12ac1486-0140-ee3c-4f5b-93472ba90a7e.gif)

`animatableData`にtextの値をDouble型で設定することで、
アニメーションするときには数字としてアニメーションしてくれるのでgifのようにカウントアップするアニメーションになります。
上記のような`animatableData`を設定しなければ、アニメーションは以下のようになります。

![TextAnimatableNormal.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/10d88aac-c71d-b997-d51d-e9cc86d40ac3.gif)

# 終わり
`animatableData`のさわりの部分しかキャッチアップできていないのですが、どういったものなのかふわっとわかった気になりました。
アニメーションしない場合は`animatableData`が設定できてないのではとか考えられそうです。
アニメーションの方法についても、`animatableData`で制御できるところがあるのでアニメーションの実装の幅が増えました。

# 参考
https://qiita.com/takehito-koshimizu/items/786dac0741e19552907b
https://medium.com/@bancarel.paul/swiftui-animatable-api-620311791f8a
https://digitalbunker.dev/mastering-animatable-and-animatablepair-swiftui/