Along with the PhaseAnimator
, SwiftUI launched the KeyframeAnimator
in iOS 17, permitting builders to create superior animations utilizing keyframes. On this tutorial, we are going to delve into the KeyframeAnimator
and discover ways to create a extra intricate animation.
The PhaseAnimator
view (or modifier), which we mentioned within the earlier tutorial, offers builders with the power to create multi-step animations over a sequence of phases. By specifying the specified animations for every part, the PhaseAnimator
routinely animates the content material at any time when the part adjustments. It simplifies the method of constructing advanced animations by dealing with the transitions between phases for you.
Working with KeyframeAnimator
For phase-based animation, it really works nicely for animations that may be represented as discrete states. When a state transition occurs, all properties are animated concurrently. As soon as the animation for a specific state is accomplished, SwiftUI easily transitions to the subsequent state. This course of continues throughout all animation phases.
Keyframe-based animation is designed to accommodate a selected kind of animation the place every property is animated independently. By using keyframes, we are able to animate particular person properties individually, which in flip provides us higher flexibility and management over our animations.

Let’s attempt to animate an emoji icon (as illustrated above) and you’ll perceive how we are able to use keyframe animator.
Defining the Animation Values
As talked about earlier, keyframe-based animation allows us to animate particular person properties independently. To make the most of the keyframe animator, we start by defining a struct that encompasses all of the properties we want to animate. Right here’s an instance:
struct AnimationValues { var scale = 1.0 var verticalStretch = 1.0 var translation = CGSize.zero var opacity = 1.0 } |
The preliminary values outline the preliminary state of the emoji icon. Later, we are going to change every of the properties to scale, stretch, and transfer the emoji icon.
Making use of the Keyframe Animator
Within the physique
closure, let’s replace the code like this to use the keyframe animator:
content material
.scaleEffect(worth.scale)
.scaleEffect(y: worth.verticalStretch)
.offset(worth.translation)
.opacity(worth.opacity)
} keyframes: { _ in
KeyframeTrack(.scale) {
CubicKeyframe(0.8, length: 0.2)
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Textual content(“🐻”) .font(.system(dimension: 100)) .keyframeAnimator(initialValue: AnimationValues()) { content material, worth in
content material .scaleEffect(worth.scale) .scaleEffect(y: worth.verticalStretch) .offset(worth.translation) .opacity(worth.opacity)
} keyframes: { _ in
KeyframeTrack(.scale) { CubicKeyframe(0.8, length: 0.2) } } |
As ordinary, we use a Textual content
view to show the emoji icon. To create a keyframe-based animation, we connect the keyframeAnimator
modifier to the textual content view.
The initialValue
parameter is supplied with the preliminary values that the keyframes will animate from. Inside the view builder closure, we now have entry to 2 parameters. The primary parameter is a proxy worth that represents the modified view. The second parameter holds the interpolated worth generated by the keyframes.
We apply the specified animation results to the content material view by adjusting its scale, offset, and opacity. Lastly, it’s the keyframes
parameter. That is the place we outline the worth adjustments that happen over time. These outlined keyframes will likely be chargeable for making use of the corresponding animations to the required worth.
Keyframes are organized into tracks, with every observe governing a definite property of the animated kind. Within the supplied code snippet, we particularly designated the keyframe observe for the dimensions property utilizing the CubicKeyframe
kind. We regulate the scale of the emoji, lowering it to 80% of its unique dimension.

When you’ve made the code adjustments, it’s best to be capable of see the animation immediately within the preview canvas. The keyframe animator animates the scale change and repeats it constantly.
Keyframe Varieties
Whereas we use the CubicKeyframe
kind, there are literally 4 various kinds of keyframes obtainable:
LinearKeyframe
– it interpolates linearly in vector house from the earlier keyframe.SpringKeyframe
– makes use of a spring perform to interpolate to the goal worth from the earlier keyframe.CubicKeyframe
– makes use of a cubic Bézier curve to interpolate between keyframes.MoveKeyframe
– instantly jumps to a worth with out interpolation.
Attempt to discover and check completely different keyframe sorts and durations to see their behaviors in motion. By experimenting with varied keyframe sorts and adjusting the length, you possibly can acquire a deeper understanding of how they impression and form your animations.
At present, we solely apply a single change for the scale
property. You might be free to outline different worth adjustments over time. Right here is an instance:
KeyframeTrack(.scale) { CubicKeyframe(0.8, length: 0.2) CubicKeyframe(0.6, length: 0.3) CubicKeyframe(1.0, length: 0.3) CubicKeyframe(0.8, length: 0.2) CubicKeyframe(0.6, length: 0.3) CubicKeyframe(1.0, length: 0.3) } |
The code describes the dimensions issue at particular instances throughout the animation. Within the preview canvas, you’ll discover a smoother and extra fluid animation for the emoji icon.

A number of Keyframe Tracks
Up till now, we now have centered on a single keyframe observe to change the dimensions issue. Nonetheless, keyframes present the power to animate a number of results independently by defining separate tracks, every with its personal distinctive timing. By incorporating a number of tracks, we are able to concurrently animate varied properties, enabling us to create extra superior animations.
In the identical demo, we are able to outline separate keyframe tracks for the vertical stretch, translation, and opacity properties. Right here is the pattern code:
content material
.scaleEffect(worth.scale)
.scaleEffect(y: worth.verticalStretch)
.offset(worth.translation)
.opacity(worth.opacity)
} keyframes: { _ in
KeyframeTrack(.verticalStretch) {
LinearKeyframe(1.2, length: 0.1)
SpringKeyframe(2.0, length: 0.2, spring: .snappy)
CubicKeyframe(1.05, length: 0.3)
CubicKeyframe(1.2, length: 0.2)
CubicKeyframe(1.1, length: 0.32)
CubicKeyframe(1.2, length: 0.2)
CubicKeyframe(1.05, length: 0.25)
CubicKeyframe(1.3, length: 0.23)
CubicKeyframe(1.0, length: 0.3)
}
KeyframeTrack(.scale) {
CubicKeyframe(0.8, length: 0.2)
CubicKeyframe(0.6, length: 0.3)
CubicKeyframe(1.0, length: 0.3)
CubicKeyframe(0.8, length: 0.2)
CubicKeyframe(0.6, length: 0.3)
CubicKeyframe(1.0, length: 0.3)
}
KeyframeTrack(.translation) {
SpringKeyframe(CGSize(width: 100, peak: 100), length: 0.4)
SpringKeyframe(CGSize(width: -50, peak: -300), length: 0.4)
SpringKeyframe(.zero, length: 0.2)
SpringKeyframe(CGSize(width: -50, peak: 200), length: 0.3)
SpringKeyframe(CGSize(width: -90, peak: 300), length: 0.3)
SpringKeyframe(.zero, length: 0.4)
}
KeyframeTrack(.opacity) {
LinearKeyframe(0.5, length: 0.2)
LinearKeyframe(1.0, length: 0.23)
LinearKeyframe(0.7, length: 0.25)
LinearKeyframe(1.0, length: 0.33)
LinearKeyframe(0.8, length: 0.2)
LinearKeyframe(1.0, length: 0.23)
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
Textual content(“🐻”) .font(.system(dimension: 100)) .keyframeAnimator(initialValue: AnimationValues()) { content material, worth in
content material .scaleEffect(worth.scale) .scaleEffect(y: worth.verticalStretch) .offset(worth.translation) .opacity(worth.opacity)
} keyframes: { _ in KeyframeTrack(.verticalStretch) { LinearKeyframe(1.2, length: 0.1) SpringKeyframe(2.0, length: 0.2, spring: .snappy) CubicKeyframe(1.05, length: 0.3) CubicKeyframe(1.2, length: 0.2) CubicKeyframe(1.1, length: 0.32) CubicKeyframe(1.2, length: 0.2) CubicKeyframe(1.05, length: 0.25) CubicKeyframe(1.3, length: 0.23) CubicKeyframe(1.0, length: 0.3) }
KeyframeTrack(.scale) { CubicKeyframe(0.8, length: 0.2) CubicKeyframe(0.6, length: 0.3) CubicKeyframe(1.0, length: 0.3) CubicKeyframe(0.8, length: 0.2) CubicKeyframe(0.6, length: 0.3) CubicKeyframe(1.0, length: 0.3) }
KeyframeTrack(.translation) { SpringKeyframe(CGSize(width: 100, peak: 100), length: 0.4) SpringKeyframe(CGSize(width: –50, peak: –300), length: 0.4) SpringKeyframe(.zero, length: 0.2) SpringKeyframe(CGSize(width: –50, peak: 200), length: 0.3) SpringKeyframe(CGSize(width: –90, peak: 300), length: 0.3) SpringKeyframe(.zero, length: 0.4) }
KeyframeTrack(.opacity) { LinearKeyframe(0.5, length: 0.2) LinearKeyframe(1.0, length: 0.23) LinearKeyframe(0.7, length: 0.25) LinearKeyframe(1.0, length: 0.33) LinearKeyframe(0.8, length: 0.2) LinearKeyframe(1.0, length: 0.23) } } |
By using a number of keyframe tracks, we are able to obtain an intriguing animation impact. On this case, the emoji icon will transfer round randomly, whereas its opacity varies at particular cut-off dates.

By default, keyframe animator retains working the animation constantly. If you wish to cease the animation, you possibly can set the repeat
parameter to false
.
You should use ZStack
and overlay one other emoji icon to create animation as proven beneath. I’ll depart this as an train so that you can discover and implement.

Abstract
KeyframeAnimator
is a useful function launched in iOS 17. In distinction to the PhaseAnimator
modifier, this new device in SwiftUI empowers builders to create superior animations utilizing keyframes.
By leveraging keyframes, builders can outline particular cut-off dates and exactly manipulate the properties of their animations. This enhanced management permits for the creation of intricate and dynamic visible results, leading to a extra fluid animations.
If you happen to discover this tutorial helpful, don’t neglect to take a look at our Mastering SwiftUI guide to dive deeper into SwiftUI.