チュートリアルから一歩踏み出したSwiftUIのカスタムAnimationの作り方ーその１（AnimaionModifier編）

#はじめに
今回はカスタムアニメーションションの作り方を書きたいと思います。
[前回ViewModifierを使った付箋](https://qiita.com/takaf51/items/8fc8834b2fe03848222e)を作りましたが、今回はその付箋部分をアニメーションさせたいと思います。
アニメーション部分に特化するため、形状は角丸のないシンプルなものを土台にします。

![ezgif.com-video-to-gif-3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24726/74abe866-6b5e-67bd-d1bf-e247860dad59.gif)

#カスタムアニメーション実装の手順
今回のアニメーションで変更させなければならない値をまずみてみましょう。

```swift:PartOfViewModifier.swift
    struct StickyNoteModifier: ViewModifier {
        var ceaseSize: CGFloat = 50.0
```
`ViewModifier`内の`ceaseSize: CGFloat`の値を変更すればアニメーションすることになります。

#で、どうやって？
この`ViewModifier`をアニメーションさせるためには下記の実装が必要となります。
1. `ViewModifier`を`Animatable`にする。
2. `ceaseSize: CGFloat`がアニメーションの対象であることを宣言する。
の以上の２つです。

まず１つ目、『`ViewModifier`を`Animatable`にする』ですが、超簡単です。`ViewModifier`を`AnimatableModifier`に書き換えるだけです。これでこの`ViewModifier`は`Animatable`になります。

```swift:PartOfAnimatableModifier.swift
    struct StickyNoteModifier: AnimatableModifier {
        var ceaseSize: CGFloat = 50.0
```

２つ目はの`ceaseSize: CGFloat`ですが、`computed property`の`animatableData`を実装するだけです。
この辺りは次回ですこし詳しく説明したいともいますが、とりあえず実装してみましょう。

```swift:PartOfAnimatableModifier-animatableData.swift
    var animatableData: CGFloat {
        get { ceaseSize}
        set {ceaseSize = newValue}
    }
```

これで終わりです。

あとはViewの設定部分でModifierとアニメーションの設定で終わりです。
下記の例では1秒間隔で折り返しの幅を0<->50の間でアニメーションさせています。

```swift:
struct StickyNoteView: View {
    @State private var widthCease:Bool = true
    var body: some View {
        Text("This is AnimatableStickyNoteView Sample")
            .stickyNote(color: .green, ceaseSize: widthCease ? 50 : 0, shadowLength: 10)
            .onAppear() {
                withAnimation(Animation.easeInOut(duration: 1.0).repeatForever()) { self.widthCease.toggle() }
            }
    }
}
```
とても簡単で独自のアニメーションの実装が可能ですね。


#コード全文
```swift:AnimationModifier.swift
import SwiftUI

struct StickyNoteView: View {
    @State private var widthCease:Bool = true
    var body: some View {
        VStack{
            Spacer()
            Text("If today were the last day of my life, would I want to do what I am about to do today")
                .font(.headline)
                .lineLimit(5)
                .frame(width: UIScreen.main.bounds.width/2)
                .stickyNote(color: .green, ceaseSize: widthCease ? 50 : 0, shadowLength: 10)
                .onAppear() {
                    withAnimation(Animation.easeInOut(duration: 1.0).repeatForever()) { self.widthCease.toggle() }
                }
            Spacer()
        }
    }
}

extension View {
    func stickyNote(color: Color, ceaseSize: CGFloat, shadowLength: CGFloat)->some View {
        self.modifier(StickyNoteModifier(color: color, ceaseSize: ceaseSize, shadowLength: shadowLength))
    }
}

struct StickyNoteModifier: AnimatableModifier {
    var color: Color = .green
    var ceaseSize: CGFloat = 50.0
    var shadowLength: CGFloat = 10.0
    private let divide: CGFloat = 2
    private let paddingHorizontal: CGFloat = 30.0
    private let paddingVertical: CGFloat = 40.0
    
    var animatableData: CGFloat {
        get { ceaseSize}
        set {ceaseSize = newValue}
    }

    func body(content:Content) -> some View {
        ZStack(content: {
            content
                .padding(.horizontal, 30)
                .padding(.vertical, 40)
                .background(StickyNote())
                .shadow(radius: shadowLength)
        })
    }
    
    // StickyNote drawing
    func StickyNote() -> some View {
        return  GeometryReader { geometry in
            ZStack {
                Path { path in
                    let w = geometry.size.width
                    let h = geometry.size.height
                    let d = min(w/self.divide, h/self.divide)
                    let m = min(d, self.ceaseSize)

                    path.move(to: CGPoint(x: 0, y: 0))
                    path.addLine(to: CGPoint(x: 0, y: h))
                    path.addLine(to: CGPoint(x: w-m, y: h))
                    path.addLine(to: CGPoint(x: w, y: h-m))
                    path.addLine(to: CGPoint(x: w, y: 0))
                    path.addLine(to: CGPoint(x: 0, y: 0))
                }
                .fill(self.color)

                Path { path in
                    let w = geometry.size.width
                    let h = geometry.size.height
                    let d = min(w/self.divide, h/self.divide)
                    let m = min(d, self.ceaseSize)

                    path.move(to: CGPoint(x: w-m, y: h))
                    path.addLine(to: CGPoint(x: w-m, y: h-m))
                    path.addLine(to: CGPoint(x: w, y: h-m))
                    path.addLine(to: CGPoint(x: w-m, y: h))
                }
                .fill(Color.black).opacity(0.4)
            }
        }
    }
}

struct StickyNoteView_Previews: PreviewProvider {
    static var previews: some View {
        StickyNoteView()
    }
}
```