A plain ViewModifier cannot animate its own behavior, and conforming it to Animatable
alone does nothing. AnimatableModifier combines the two protocols so SwiftUI re-runs the
modifier's body on every animation frame, which animates things SwiftUI cannot animate by
itself, such as the digits in a text view. Available since iOS 13.
Animating a Number
Expose the value to interpolate through animatableData. SwiftUI drives that property
through the animation curve and calls body with each intermediate value.
struct NumberView: AnimatableModifier {
var number: Int
var animatableData: CGFloat {
get { CGFloat(number) }
set { number = Int(newValue) }
}
func body(content: Content) -> some View {
Text(String(number))
}
}
The content parameter is usually what a modifier decorates; here the modifier ignores it
and returns a fresh Text each frame, because the text itself is what changes.
Driving It
Mutate the state inside withAnimation as usual. The count-up effect comes entirely from the
interpolation of animatableData.
struct ContentView: View {
@State private var number = 0
var body: some View {
VStack {
Text(String(number))
.modifier(NumberView(number: number))
Button("Animate") {
withAnimation(.easeInOut(duration: 2)) {
number = 100
}
}
}
}
}
animatableData must be a VectorArithmetic type such as CGFloat or AnimatablePair;
convert integers at the boundary as above. On newer SDKs AnimatableModifier is deprecated
in favor of conforming a ViewModifier to Animatable directly, and the animatableData
pattern is unchanged.