SwiftUI Field Guide

Basic Keyframe Animations

Keyframe animations allow us to animate values, not views. These values can then be used to animate our views. With keyframes, we specify the key values and their duration, and the system takes care of interpolating these during the animation. At the moment of writing, there are four different keyframes:

Here is a view that wiggles when tapped. The wiggle is implemented by multiple keyframes. When we use CubicKeyframe, we can see that the system draws a smooth line through the specified points. With linear keyframes, the line also looks smooth, as subsequent keyframes are blended. However, when we look at the velocity, we can see it gets close to zero at the key points. We can also build this wiggle with phase animations or an Animatable modifier.

Code
struct RotationWiggle: View {    @State var trigger = 0        var body: some View {        RoundedRectangle(cornerRadius: 12)            .fill(.red)            .frame(width: 80, height: 96)            .onTapGesture {                trigger += 1            }            .keyframeAnimator(                initialValue: 0.0,                trigger: trigger            ) { rectangle, angle in                rectangle                    .rotationEffect(.degrees(                                        angle                                    ))            } keyframes: { _ in                KeyframeTrack(\.self) {                    CubicKeyframe(-15,                                  duration: 0.1)                    CubicKeyframe(12,                                  duration: 0.15)                    CubicKeyframe(-8,                                  duration: 0.15)                    CubicKeyframe(4,                                  duration: 0.15)                    CubicKeyframe(0,                                  duration: 0.1)                }            }    }}
Preview
Loading preview…
Track
Angle (°) over time-3003000.3250.65Time (s)

Drag points vertically to change values. Drag the lower handles to change timing. Arrow keys edit a selected point; hold Shift for larger steps. Delete removes an interior point.

6 points.
0.00 s

One of the most useful features of keyframes is that we can have multiple tracks. In the example below, we have a classic bouncing ball animation. At rest, the ball is bottom-aligned in the frame. We animate the vertical scale and vertical offset and apply it to the view using keyframeAnimator. The keyframes are defined below.

Code
struct AnimationValues {    var verticalOffset = 0.0    var verticalScale = 1.0}struct BouncingBall: View {    @State var trigger = 0        var body: some View {        Circle()            .fill(.red)            .frame(width: 56, height: 56)            .onTapGesture {                trigger += 1            }            .keyframeAnimator(                initialValue: AnimationValues(),                trigger: trigger            ) { ball, value in                ball                    .scaleEffect(                        x: 1 / value.verticalScale,                        y: value.verticalScale,                        anchor: .bottom                    )                    .offset(                        y: value.verticalOffset                    )            } keyframes: { _ in                keyframes            }            .frame(maxWidth: .infinity,                   maxHeight: .infinity,                   alignment: .bottom)            .overlay(alignment: .bottom) {                Rectangle()                    .fill(.tertiary)                    .frame(height: 1)            }    }}

We use linear keyframes for the vertical offset and cubic keyframes for the vertical scale. You can play around with the values in the diagram (change the duration and scalar values) to see how they affect the animation.

Keyframes
@KeyframesBuilder<AnimationValues>var keyframes: some Keyframes<AnimationValues> {    KeyframeTrack(\.verticalOffset) {        LinearKeyframe(0, duration: 0.15)        LinearKeyframe(-140,                       duration: 0.4,                       timingCurve: .easeOut)        LinearKeyframe(0,                       duration: 0.4,                       timingCurve: .easeIn)        LinearKeyframe(0, duration: 0.35)    }        KeyframeTrack(\.verticalScale) {        CubicKeyframe(0.7, duration: 0.15)        CubicKeyframe(1.15, duration: 0.15)        CubicKeyframe(1, duration: 0.25)        CubicKeyframe(1.15, duration: 0.4)        CubicKeyframe(0.65, duration: 0.08)        CubicKeyframe(1, duration: 0.12)        LinearKeyframe(1, duration: 0.15)    }}
Preview
Loading preview…
Tracks
Vertical offset over time-150-100-500

Drag points vertically to change values. Drag the lower handles to change timing. Arrow keys edit a selected point; hold Shift for larger steps. Delete removes an interior point.

5 points.
Vertical scale over time0.511.500.651.3Time (s)

Drag points vertically to change values. Drag the lower handles to change timing. Arrow keys edit a selected point; hold Shift for larger steps. Delete removes an interior point.

8 points.
0.00 s

Resources

Share Image

Four Ways to Shake compares keyframe and phase animations, including their velocity curves.

https://chris.eidhof.nl/post/four-ways-to-shake/
Share Image

Apple's Wind your way through advanced animations in SwiftUI (WWDC23). The keyframes section starts at 8:12.

https://developer.apple.com/videos/play/wwdc2023/10157/
Share Image

Apple's Explore SwiftUI animation (WWDC23).

https://developer.apple.com/videos/play/wwdc2023/10156/
Share Image

Thinking in SwiftUI, Animations chapter: “Keyframe-Based Animations.”

https://www.objc.io/books/thinking-in-swiftui/