Getting Started with SwiftUI Animations
In this tutorial, you’ll learn how to add fancy animations with SwiftUI. You’ll go from basic animations to complex and custom spring animations. By Michael Katz.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Getting Started with SwiftUI Animations
30 mins
- Getting Started
- Preview Window
- Basic Animations
- Animation Timing
- Tinkering With Timing
- Simultaneous Animations
- Animating State Changes
- Transitions
- Combining Transitions
- Asynchronous Transitions
- Springs
- Using SwiftUI’s Pre-Built Spring Options
- Refining the Animation
- Animatable
- An Alternate Look at Animatable Data
- Other Ways to Animate
- Where to Go From Here?
Tinkering With Timing
If you’re having trouble seeing the subtle differences, you can slow down the animation by making it take more time. Replace the animation
modifier with:
.animation(.easeIn(duration: 5))
Specifying a longer duration will make the timing curve more noticeable.
An advantage of building and running in the simulator instead of the SwiftUI preview window is that you can enable the Debug ▸ Slow Animations flag. This will drastically slow down any animation so you can see the subtle differences more clearly. This way, you don’t have to add extra duration
parameters.
You can use a few other levers to control the timing of an animation, besides the timing curve and duration. First, there’s speed
.
Create a new property at the top of ContentView
:
let moonAnimation = Animation.easeInOut.speed(0.1)
This constant just stores the animation so you can use it more easily in the code later. It also gives you a single spot to change things around.
Next, replace the Image
animation modifier:
.animation(moonAnimation)
Now, the moon will resize very slowly. Try changing the speed to 2
and the animation will be quite zippy.
In addition to speed, you can also add delay
. Change moonAnimation
to:
let moonAnimation = Animation.easeInOut.delay(1)
Now, the animation has a one-second delay. Tapping the row flashes in the moon list, then later, the button will change size. Delay
is most useful when animating multiple properties or objects at once, as you’ll see later.
Finally, you can use modifiers to repeat an animation. Change the animation to:
let moonAnimation = Animation.easeInOut.repeatForever(autoreverses: true)
The button will now pulse forever. You can also use repeatCount(autoreverses:)
to repeat the animation a limited number of times.
When you’re done experimenting, set the animation back to:
let moonAnimation = Animation.default
Simultaneous Animations
.animation
is a modifier that stacks on to a SwiftUI View
like any other. If a view has multiple changing attributes, a single Animation
can apply to all of them.
Add the following rotation effect by placing it between the Image
and scaleEffect
lines:
.rotationEffect(.degrees(self.toggleMoons(planet.name) ? -50 : 0))
This adds a little rotation to the moon button so the crescent lines up sideways when the moon view appears. These will animate together since you add the animation at the end.
Of course, you can specify separate animations for each attribute. For example, add the following modifier after the rotationEffect
modifier:
.animation(.easeOut(duration: 1))
This gives the rotation a one-second animation, so you’ll notice the rotation ending slightly later compared to the scaling effect. Next, change moonAnimation
to:
let moonAnimation = Animation.default.delay(1)
This delays the size animation by one second, so the scale starts after the rotation finishes. Try it out.
Finally, you can choose not to animate a particular attribute change by specifying nil
for animation. Change the rotationEffect
animation to:
.animation(nil)
And change the moonAnimation
back to:
let moonAnimation = Animation.default
Now, only the size animates.
Animating State Changes
You’ve spent a while perfecting the animation of the moon button, but what about that big view that drops in with all the circles?
Well, you can animate any state transition with a simple withAnimation
block. Replace the content of the button’s action block with:
withAnimation {
self.showMoon = self.toggleMoons(planet.name) ? nil : planet.name
}
withAnimation
explicitly tells SwiftUI what to animate. In this case, it animates the toggling of showMoon
and any views that have attributes that depend on it.
If you watch the animation now, you’ll see the moons view fade in using the default appear animation and the list row slides out of the way to make room for it.
You can also supply a specific animation to the explicit animation block. Replace withAnimation
with:
withAnimation(.easeIn(duration: 2))
This now uses an eased-in animation with a two-second duration instead of the default.
If you try this out, the slowed animation probably doesn’t look right. There are better ways to animate views coming in and out of the hierarchy.
Before moving on, change the withAnimation
to:
withAnimation(.easeInOut)
Transitions
A transition covers how a view is inserted or removed. To see how it works, add this modifier to the MoonList
in the if self.toggleMoons(planet.name)
block:
.transition(.slide)
Now, instead of fading in, the view will slide in. Slide
animates in from the leading edge and out through the trailing.
Transitions are of the AnyTransition
type. SwiftUI comes with a few pre-made transitions:
- .slide: You’ve already seen this one in action — it slides the view in from the side.
- .opacity: This transition fades the view in and out.
- .scale: This animates by enlarging or shrinking the view.
-
.move: This is like
slide
, except that you can specify the edge. - .offset: Moves the view in an arbitrary direction.
Go ahead and try some of these transitions to get a sense of how they work.
Combining Transitions
You can also combine transitions to compose your very own custom effects. At the top of ContentView.swift, add this extension:
extension AnyTransition {
static var customTransition: AnyTransition {
let transition = AnyTransition.move(edge: .top)
.combined(with: .scale(scale: 0.2, anchor: .topTrailing))
.combined(with: .opacity)
return transition
}
}
This combines three transitions: a move
from the top
edge, a scale
from 20% anchored to the top trailing corner and an opacity
fade effect.
To use it, change the transition
line at the MoonList
instance to:
.transition(.customTransition)
Build and run your project and try opening a moon list. The combined effect is as if the view swoops in and out from the moon button.
Asynchronous Transitions
If you want, you can also make your entrance transition different from your exit transition.
In ContentView.swift, replace the definition of customTransition
with:
static var customTransition: AnyTransition {
let insertion = AnyTransition.move(edge: .top)
.combined(with: .scale(scale: 0.2, anchor: .topTrailing))
.combined(with: .opacity)
let removal = AnyTransition.move(edge: .top)
return .asymmetric(insertion: insertion, removal: removal)
}
This keeps the swoop in insertion, but now the moon view exits the screen by moving to the top.
Springs
Right now, the moons list just shows the moons stacked on top of each other in concentric circles. It would look a lot nicer if they were spaced out, and maybe even animated in.
In MoonView.swift, add the following modifier to the end of the body
chain:
.onAppear {
withAnimation {
self.angle = self.targetAngle
}
}
This causes a random angle to be set on the orange ball representing the moon. The moon will then animate from zero degrees on the circle to the new location, in a straight line.
This animation isn’t awesome yet, so you’ll need to add a little bit more pizazz. Spring animations allow you to add a little bounce and jiggle to make your views feel alive.
In SolarSystem.swift add this method to SolarSystem
:
func animation(index: Double) -> Animation {
return Animation.spring(dampingFraction: 0.5)
}
This helper method creates a spring animation.
Then, in makeSystem(_:)
, in the last ForEach
, append the following modifier to the self.moon
line:
.animation(self.animation(index: Double(index)))
This adds the animation to each of the moon circles. Don’t worry about the index
right now; you’ll use it in the next section.
Next time you load the view, there will be a little bounce as the moons take their final positions.