LoginSignup
9
4

More than 3 years have passed since last update.

SwiftUI で Animatable なシェイプを作ってみる

Last updated at Posted at 2020-07-06

シェイプにアニメーションをつけてみました

SwiftUI の勉強をかねて、以前の記事で作成した SwiftUI のシェイプにアニメーションをつけてみました。

環境

  • Xcode Version 11.5 (11E608c)

参考記事

今回はこちらの記事を参考にして、以前作成したシェイプにアニメーションをつけてみました。
動画が非常にわかりやすくて参考になりました。

Animating simple shapes with animatableData - a free Hacking with iOS: SwiftUI Edition tutorial
https://www.hackingwithswift.com/books/ios-swiftui/animating-simple-shapes-with-animatabledata

Animating complex shapes with AnimatablePair - a free Hacking with iOS: SwiftUI Edition tutorial
https://www.hackingwithswift.com/books/ios-swiftui/animating-complex-shapes-with-animatablepair

Shape はもともと Animatable

まずは Xcode で Shape の定義にジャンプしてみます。

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol Shape : Animatable, View {

    /// Describes this shape as a path within a rectangular frame of reference.
    ///
    /// - Parameter rect: The frame of reference for describing this shape.
    /// - Returns: A path that describes this shape.
    func path(in rect: CGRect) -> Path
}

実は Shape はもともと Animatable のサブタイプであることがわかります。
更に、 Animatable にジャンプしてみます。

/// A type that can be animated
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol Animatable {

    /// The type defining the data to be animated.
    associatedtype AnimatableData : VectorArithmetic

    /// The data to be animated.
    var animatableData: Self.AnimatableData { get set }
}

AnimatableanimatableData という1つのプロパティを持ち、
これを実装することで、アニメーションをつけることができみたいです。

自作のシェイプにアニメーションをつける

以前の記事で作成した StarShape のプロパティ smoothness にアニメーションをつけてみます。

import SwiftUI

struct StarShape: Shape {
    var vertex: UInt = 5
    var smoothness: Double = 0.5
    var rotation: CGFloat = -.pi/2

    var animatableData: Double {
        get { smoothness }
        set { smoothness = newValue }
    }

    func path(in rect: CGRect) -> Path {
        Path { path in
            let points: [CGPoint] = StarParameters(vertex: vertex, smoothness: smoothness)
                .center(x: rect.midX, y: rect.midY)
                .radius(min(rect.midX, rect.midY))
                .rotated(by: rotation)
                .cgPoints
            path.move(to: points.first!)
            points.forEach { point in
                path.addLine(to: point)
            }
            path.closeSubpath()
        }
    }
}

ポイントはここ。

var animatableData: Double {
    get { smoothness }
    set { smoothness = newValue }
}

animatableDataget, set でプロパティを指定します。
たったこれだけです。

早速、このシェイプを使ってみます。

import SwiftUI

private let style = LinearGradient(
    gradient: Gradient(colors: [
        Color(red: 239/255, green: 120.0/255, blue: 221/255),
        Color(red: 239/255, green: 172.0/255, blue: 120/255)
    ]),
    startPoint: UnitPoint(x: 0.5, y: 0),
    endPoint: UnitPoint(x: 0.5, y: 0.6)
)

struct StarView: View {
    /// 頂点の数
    var vertex: UInt = 5
    /// 滑らかさ
    @State var smoothness: Double = 0.5

    var body: some View {
        let shape = StarShape(vertex: vertex, smoothness: smoothness)
        return ZStack {
            shape.fill(style)
            shape.stroke(Color.black, lineWidth: 4)
        }
        .aspectRatio(1.1, contentMode: .fit)
        .onTapGesture {
            withAnimation {
                self.smoothness = Double(Int(self.smoothness * 10 + 1) % 5 + 5)/10
            }
        }
    }
}

ビューのタップジェスチャーで smoothness を書換えて、再描画します。
アニメーションさせたいので、withAnimation 関数で囲んでいます。

複数のプロパティにアニメーションをつける

StarShape のプロパティ vertex もアニメーション可能にしてみます。
VectorArithmetic に適合している AnimatablePair を利用します。

import SwiftUI

struct StarShape: Shape {
    var vertex: UInt = 5
    var smoothness: Double = 0.5
    var rotation: CGFloat = -.pi/2

    var animatableData: AnimatablePair<Double, Double> {
        get {
            AnimatablePair(Double(vertex), smoothness)
        }
        set {
            vertex = UInt(newValue.first)
            smoothness = newValue.second
        }
    }
    /* 以下省略 */
}

このようなペアを返すことで animatableData に複数のプロパティを参照させることができます。
次のように、型パラメータに AnimatablePair を指定することで、3つ以上プロパティをアニメーション可能にすることもできるみたいです。

// 3つ
AnimatablePair<Double, AnimatablePair<Double, Double>>
// 4つ
AnimatablePair<Double, AnimatablePair<Double, AnimatablePair<Double, Double>>>
// 5つ
AnimatablePair<Double, AnimatablePair<Double, AnimatablePair<Double, AnimatablePair<Double, Double>>>>

このシェイプを使って、頂点の増減にもアニメーションをかけてみます。
サンプルコードは次の通りです。

import SwiftUI

private let style = LinearGradient(
    gradient: Gradient(colors: [
        Color(red: 239/255, green: 120.0/255, blue: 221/255),
        Color(red: 239/255, green: 172.0/255, blue: 120/255)
    ]),
    startPoint: UnitPoint(x: 0.5, y: 0),
    endPoint: UnitPoint(x: 0.5, y: 0.6)
)

struct StarView: View {
    /// 頂点の数
    @State var vertex: UInt = 5
    /// 滑らかさ
    @State var smoothness: Double = 0.5

    var body: some View {
        let shape = StarShape(vertex: vertex, smoothness: smoothness)
        return ZStack {
            shape.fill(style)
            shape.stroke(Color.black, lineWidth: 4)
        }
        .aspectRatio(1.1, contentMode: .fit)
        .onTapGesture {
            withAnimation {
                self.vertex = ((self.vertex + 3) % 5) + 5
                self.smoothness = Double(Int(self.smoothness * 10 + 1) % 5 + 5)/10
            }
        }
    }
}

まとめ

単純な図形であれば、簡単にアニメーションの実装ができることがわかりました。

  • アニメーション可能なビューにするためには Animatable に適合する
  • Shape はもともと Animatable
  • AnimatablePair で複数の属性をアニメーション可能にすることができる
9
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
9
4