7
4

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-11-22

はじめに

今回はカスタムアニメーションションの作り方を書きたいと思います。
前回ViewModifierを使った付箋を作りましたが、今回はその付箋部分をアニメーションさせたいと思います。
アニメーション部分に特化するため、形状は角丸のないシンプルなものを土台にします。

ezgif.com-video-to-gif-3.gif

カスタムアニメーション実装の手順

今回のアニメーションで変更させなければならない値をまずみてみましょう。

PartOfViewModifier.swift
    struct StickyNoteModifier: ViewModifier {
        var ceaseSize: CGFloat = 50.0

ViewModifier内のceaseSize: CGFloatの値を変更すればアニメーションすることになります。

で、どうやって?

このViewModifierをアニメーションさせるためには下記の実装が必要となります。
1. ViewModifierAnimatableにする。
2. ceaseSize: CGFloatがアニメーションの対象であることを宣言する。
の以上の2つです。

まず1つ目、『ViewModifierAnimatableにする』ですが、超簡単です。ViewModifierAnimatableModifierに書き換えるだけです。これでこのViewModifierAnimatableになります。

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

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

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

これで終わりです。

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

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() }
            }
    }
}

とても簡単で独自のアニメーションの実装が可能ですね。

コード全文

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()
    }
}
7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4