Pathの形状をアニメーションで変更する (SwiftUI)

SwiftUIはアニメーション関連の機能が充実しており、[animation(_:value:)](https://developer.apple.com/documentation/swiftui/view/animation(_:value:))や[withAnimation(_:_:)](https://developer.apple.com/documentation/swiftui/withanimation(_:_:))などで、簡単にUIのインタラクションを実現できます。

自分が思いつく限りのインタラクションは大体それらで事足ります。

ただ最近、他のアプリでちょっと面白い動きをするUIを見かけ、どうやっているんだろうと考えているうちに、拡大・縮小など`animation`でいじれる範囲のプロパティーではその動きは実現できそうにない、という結論に達しました。

UIの形状自体を変える必要がありそうです。

そのアプリはおそらくSwiftUIでなく、Unityとかその辺りを使っていると思われますが、自分はその辺に疎いのでSwiftUIでなんとか実現したいと思いました。

その下準備として、SwiftUIで「Pathのポイントをアニメーションで移動させて形状を変える」というのを試してみることにします。

具体的にはこういった感じです。

![画面収録 2023-03-26 16.00.58.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2763088/80b1a573-b711-3846-84d9-4c6d11acd02d.gif)

円(正確には30角形)と矩形をアニメーションで遷移させています。

ソースに関しては[こちら](https://qiita.com/masaru-honma/items/fc9b3ca098103a1530f7#ソースコード)で、Playgroundにコピペで実行できます。

---

普段は`animation`とかを追加するだけなので、アニメーションがどう実行されているかに関しては気にしたことがなかったのですが、今回初めて触れることができました。

アニメーションを自前で実装する際は[animatableData](https://developer.apple.com/documentation/swiftui/animatable/animatabledata-swift.property-6nydg)というプロパティが肝になるようです。

L10-
```swift
    /// アニメーション対象
    var animatableData: AnimatableCGPointVector {
        get { points }
        set { points = newValue }
    }
```

こちらは[Animatable](https://developer.apple.com/documentation/swiftui/animatable)プロトコルで指定されており、適合することで設定したプロパティがアニメーションに対応します。

`Shape`は`Animatable`を[含んでいる](https://developer.apple.com/documentation/swiftui/animatable#inherited-by)ので、そのまま`animatableData`を設定することができます。

`animatableData`に指定するプロパティはどんな型でも良いわけではなく、[VectorArithmetic](https://developer.apple.com/documentation/swiftui/vectorarithmetic)プロトコルに適合した型である必要があります。

もっと正確にいうと、指定自体はどんな型でもできるみたいですが、`VectorArithmetic`に適合した型でないとアニメーションは実行されません。

`VectorArithmetic`は、加減算を行うプロトコルである[AdditiveArithmetic](https://developer.apple.com/documentation/Swift/AdditiveArithmetic)を拡張したものであり、アニメーションの始点と終点の間の数値を補完する目的で使われているようです。

---

さてここで問題が生じます。

Pathを描画するための[CGPoint](https://developer.apple.com/documentation/corefoundation/cgpoint/)ですが、配列で利用する必要があります。

L16-
```swift
    /// パスの作成
    /// - Parameter rect: 領域
    /// - Returns: パス
    func path(in rect: CGRect) -> Path {
        .init { $0.addLines(points.values) }
    }
```

`CGPoint`自体は`Animatable`に[適合している](https://developer.apple.com/documentation/corefoundation/cgpoint/#conforms-to)のですが、`Array<CGPoint>`は`Animatable`に適合していません。（`Array`自体が適合していない） [^1]

そこで`Array<CGPoint>`を`Animatable`に適合させようと色々調べているうちに、その用途に使えるコードがgistで見つかりましたので、今回はそのコードをそのまま使わせてもらいました。

[The type that holds array of CGPoints and conforms to VectorArithmetic](https://gist.github.com/mecid/04ab91f45fec501e72e4d5fb02277f3f)

こちらのソースもアニメーションがどう動くかの参考になります。

---

ここまで書いてなんですが、アニメーションの概念自体はこちらのページで詳しく解説されておりますのでご参照ください。

[SwiftUI で Animatable なシェイプを作ってみる - Qiita](https://qiita.com/takehito-koshimizu/items/786dac0741e19552907b)

`AnimatableShape`の部分は円や矩形に限らず、ポイントを渡せばアニメーションにできる感じになっていますので色々使い回せそうです。

ここに曲線とかが絡んでくるとまためんどくさくなりそうですが、とりあえず勘所は掴めたのでよかったです。

## ソースコード

https://gist.github.com/HonmaMasaru/7a13f53b9ea839f63f7990638d7fc71f

[^1]:`Animatable`に適合しているので[animatableData](https://developer.apple.com/documentation/swiftui/animatable/animatabledata-swift.property-6nydg)を利用できる。`AnimatableData`はassociatedtypeとして`VectorArithmetic`が[設定されている](https://developer.apple.com/documentation/swiftui/animatable/animatabledata-swift.associatedtype)。