Animatable
In SwiftUI, we can rely on the built-in animatable properties of nodes. However, if we want to hook into the animation system ourselves, we can do so through the Animatable protocol, or in modern SwiftUI using the @Animatable macro. The macro hooks the animatable stored properties of a view or view modifier into the animation system.
Animating Numbers
For example, in this counter, if we animate an update to the Counter struct, SwiftUI will interpolate the value property for us because we marked our struct with @Animatable. We store the value as a Double and display it as an Int.
@Animatablestruct Counter: View { var value: Double var body: some View { Text("\(Int(value))") .monospacedDigit() }}struct CounterExample: View { @State private var value = 0.0 var body: some View { Counter(value: value) .frame(maxWidth: .infinity, maxHeight: .infinity) .contentShape(.rect) .onTapGesture { withAnimation(.linear( duration: 2 )) { value = value == 0 ? 100 : 0 } } }}
In the simplified attribute graph, we can see that changing the state property causes an update to propagate through its dependent nodes. When the update reaches the animatable node, SwiftUI uses the animation in the transaction to start animating its value and adds a dependency on the current animation time.
While the animation is running, this dynamic dependency causes SwiftUI to interpolate value and evaluate Counter.body as time changes. When the animation finishes, SwiftUI removes the dependency on time. This is what makes our counter animate.
Wiggle
To get full control of our animations, it's often easiest to increment a value by one and use it as progress. That's exactly what we do in this wiggle example. Our Wiggle modifier then uses a sine wave to turn that progress into a rotation.
@Animatablestruct Wiggle: ViewModifier { var progress: Double func body(content: Content) -> some View { content .rotationEffect(.degrees(sin( progress * 4 * .pi ) * 15)) }}struct WiggleExample: View { @State private var value = 0.0 var body: some View { RoundedRectangle(cornerRadius: 12) .fill(.red) .frame(width: 80, height: 96) .modifier(Wiggle(progress: value)) .frame(maxWidth: .infinity, maxHeight: .infinity) .contentShape(.rect) .onTapGesture { withAnimation(.linear( duration: 1 )) { value += 1 } } }}
Drag the playhead or use the Animation time slider to inspect the animation.
As we scrub through the track, we can see exactly how this drives our animation. On the first tap, the state property changes directly from 0 to 1, with no values in between. Because we marked Wiggle with @Animatable, SwiftUI interpolates its progress property between the old and new values. Those intermediate values drive the wiggle.
We can also build a wiggle using phase animations or keyframe animations.
Along a Path
We can also get more creative. For example, here we use the progress to find a point on a path and position our view at that point. This uses the exact same technique as the wiggle animation, just with a different effect.
@Animatablestruct FollowPath: ViewModifier { var progress: Double let path: Path func body(content: Content) -> some View { let point = path .trimmedPath(from: 0, to: progress) .currentPoint ?? .zero content .position(point) }}struct FollowPathExample: View { @State private var value = 0.0 var body: some View { let path = Path { path in path.move(to: .zero) path.addCurve(to: CGPoint(x: 140, y: 100), control1: CGPoint(x: 0, y: 180), control2: CGPoint( x: 140, y: -80 )) } Circle() .fill(.red) .frame(width: 24, height: 24) .modifier(FollowPath(progress: value, path: path)) .background { path.stroke(.tertiary, lineWidth: 2) } .frame(width: 140, height: 100) .frame(maxWidth: .infinity, maxHeight: .infinity) .contentShape(.rect) .onTapGesture { withAnimation(.linear( duration: 3 )) { value = value == 0 ? 1 : 0 } } }}
The progress value we pass to trimmedPath(from:to:) isn't exactly proportional to the distance along the path. This means our view won't move at a perfectly constant speed, but it's close enough for this example.
Resources

Thinking in SwiftUI, Animations chapter: “The Animatable Protocol,” including the custom opacity modifier and sine-based Shake example. Related sections: “Timing Curves” and “Transactions.”
https://www.objc.io/books/thinking-in-swiftui/
Swift Talk #402, Wobble Animation. The Animatable modifier starts at 8:47, with sine-based rotation at 10:47.
https://talk.objc.io/episodes/S01E402-wobble-animation
Swift Talk #165, Animating along Paths, moves a shape along a path using animatable data. #166, Geometry Effects, adds rotation along the path.
https://talk.objc.io/episodes/S01E165-animating-along-paths
Four Ways to Shake compares a custom Animatable view with phase, keyframe, and timeline animations, including their velocity curves.
https://chris.eidhof.nl/post/four-ways-to-shake/
Apple's What's new in SwiftUI (WWDC25): @Animatable and @AnimatableIgnored at 11:24.

Apple's @Animatable macro reference and Animatable protocol documentation.