シェイプにアニメーションをつけてみました
SwiftUI の勉強をかねて、以前の記事で作成した SwiftUI のシェイプにアニメーションをつけてみました。
— takehito-koshimizu (@takehitokoshim1) July 6, 2020 |
— takehito-koshimizu (@takehitokoshim1) July 6, 2020 |
環境
- 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 }
}
Animatable
は animatableData
という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 }
}
animatableData
の get
, 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
で複数の属性をアニメーション可能にすることができる