はじめに
SwiftUIのアニメーションを実装するときに時々見かけるanimatableData
何のことかさっぱりだったので、キャッチアップしてみました。
animatableDataとは
早速animatableData
とはなんなのか、animatableData
でググって一番最初に出てきた下記の記事を見てみました。
animatableData
はAnimatable
プロトコルが持つプロパティのようで、animatableData
に設定した値が変わることでその値に紐づくコンポーネントがアニメーションできるようになるみたいです。
とは言っても文面だけでみても理解しきれなかったので、サンプルコードを書いてみます。
やってみる
まずはanimatableDataを定義せずにアニメーションするのかを確認しました。
四角形のカスタムShapeをタップすると横幅と縦幅を変えるようにしました。
animatableData
でwidth
、height
を設定していないので、おそらくこれらの値を更新してもアニメーションしないはず!
struct CustomRectangleView: View {
@State private var width: CGFloat = 100
@State private var height: CGFloat = 100
var body: some View {
ZStack {
CustomShape(width: width, height: height)
}
.frame(maxWidth: width, maxHeight: height)
.onTapGesture {
withAnimation(.linear(duration: 0.5)) {
width = CGFloat.random(in: 10...200)
height = CGFloat.random(in: 10...200)
}
}
}
struct CustomShape: Shape {
var width: CGFloat
var height: CGFloat
nonisolated func path(in rect: CGRect) -> Path {
var path = Path()
path.addRect(.init(x: (rect.width - width) / 2, y: (rect.height - height) / 2, width: width, height: height))
return path
}
}
}
こちらは想定通りアニメーションせずでした。
続いて、animatableData
にwidthを設定してみます。
struct CustomRectangleView: View {
@State private var width: CGFloat = 100
@State private var height: CGFloat = 100
var body: some View {
ZStack {
CustomShape(width: width, height: height)
}
.frame(maxWidth: width, maxHeight: height)
.onTapGesture {
withAnimation(.linear(duration: 0.5)) {
width = CGFloat.random(in: 10...200)
height = CGFloat.random(in: 10...200)
}
}
}
struct CustomShape: Shape {
var width: CGFloat
var height: CGFloat
var animatableData: CGFloat { // 追加行
// 追加行
get { return width } // 追加行
set { width = newValue } // 追加行
} // 追加行
nonisolated func path(in rect: CGRect) -> Path {
var path = Path()
path.addRect(.init(x: (rect.width - width) / 2, y: (rect.height - height) / 2, width: width, height: height))
return path
}
}
}
上記例では、横幅の変更だけアニメーションし、縦幅はアニメーションしないようになっています。
ここまでの例でanimatableData
に設定したデータの変更がアニメーションするようになることがわかりました。
ただ調べてみるとそれだけではなさそうです。
animatableData
の設定の仕方によってはアニメーションの仕方も変えられるみたいです。
struct CountUpAnimationTextView: View {
@State private var text: String = "0"
var body: some View {
CountUpAnimationText(text: text)
.onAppear {
withAnimation(.linear(duration: 5)) {
text = "100"
}
}
}
}
struct CountUpAnimationText: View, Animatable {
private var text = "0"
init(text: String = "0") {
self.text = text
}
// 設定されたテキストをDouble型として参照する
var animatableData: Double {
get { Double(text) ?? 0 }
set { text = String(format: "%.0f", newValue) }
}
var body: some View {
Text(text)
.font(.system(size: 30, weight: .bold))
}
}
animatableData
にtextの値をDouble型で設定することで、
アニメーションするときには数字としてアニメーションしてくれるのでgifのようにカウントアップするアニメーションになります。
上記のようなanimatableData
を設定しなければ、アニメーションは以下のようになります。
終わり
animatableData
のさわりの部分しかキャッチアップできていないのですが、どういったものなのかふわっとわかった気になりました。
アニメーションしない場合はanimatableData
が設定できてないのではとか考えられそうです。
アニメーションの方法についても、animatableData
で制御できるところがあるのでアニメーションの実装の幅が増えました。
参考
https://qiita.com/takehito-koshimizu/items/786dac0741e19552907b
https://medium.com/@bancarel.paul/swiftui-animatable-api-620311791f8a
https://digitalbunker.dev/mastering-animatable-and-animatablepair-swiftui/