SALE: Save 50% on all my books and bundles >>

Customizing animations in SwiftUI

Paul Hudson    @twostraws   

When we attach the modifier .animation(.default) to a view, SwiftUI will automatically animate any changes that happen to that view using whatever is the default system animation. In practice, that is an “ease in, ease out” animation, which means iOS will start the animation slow, make it pick up speed, then slow down as it approaches its end.

We can control the type of animation used by passing in different values to the modifier. For example, we could use .easeOut to make the animation start fast then slow down to a smooth stop:

.animation(.easeOut)

There are even spring animations, that cause the movement to overshoot then return to settle at its target. You can control the initial stiffness of the spring (which sets its initial velocity when the animation starts), and also how fast the animation should be “damped” – lower values cause the spring to bounce back and forth for longer.

For example, this makes our button scale up quickly then bounce:

.animation(.interpolatingSpring(stiffness: 50, damping: 1))

For more precise control, we can customize the animation with a duration specified as a number of seconds. So, we could get an ease-in-out animation that lasts for two seconds like this:

struct ContentView: View {
    @State private var animationAmount: CGFloat = 1

    var body: some View {
        Button("Tap Me") {
            self.animationAmount += 1
        }
        .padding(50)
        .background(Color.red)
        .foregroundColor(.white)
        .clipShape(Circle())
        .scaleEffect(animationAmount)
        .animation(.easeInOut(duration: 2))
    }
}

When we say .easeInOut(duration: 2) we’re actually creating an instance of an Animation struct that has its own set of modifiers. So, we can attach modifiers directly to the animation to add a delay like this:

.animation(
    Animation.easeInOut(duration: 2)
        .delay(1)
)

You’ll notice that we explicitly have to say Animation.easeInOut() now, because otherwise Swift isn’t quite sure what we mean. Regardless, tapping the button will now wait for a second before executing a two-second animation.

We can also ask the animation to repeat a certain number of times, and even make it bounce back and forward by setting autoreverses to true. This creates a one-second animation that will bounce up and down before reaching its final size:

.animation(
    Animation.easeInOut(duration: 1)
        .repeatCount(3, autoreverses: true)
)

If we had set repeat count to 2 then the button would scale up then down again, then jump immediately back up to its larger scale. This is because ultimately the button must match the state of our program, regardless of what animations we apply – when the animation finishes the button must have whatever value is set in animationAmount.

For continuous animations, there is a repeatForever() modifier that can be used like this:

.animation(
    Animation.easeInOut(duration: 1)
        .repeatForever(autoreverses: true)
)

We can use these repeatForever() animations in combination with onAppear() to make animations that start immediately and continue animating for the life of the view.

To demonstrate this, we’re going to remove the animation from the button itself and instead apply it an overlay to make a sort of pulsating circle around the button.

So, first add this overlay() modifier to the button:

.overlay(
    Circle()
        .stroke(Color.red)
        .scaleEffect(animationAmount)
        .opacity(Double(2 - animationAmount))
)

That makes a stroked red circle over our button, using an opacity value of 2 - animationAmount so that when animationAmount is 1 the opacity is 1 (it’s opaque) and when animationAmount is 2 the opacity is 0 (it’s transparent).

Next, remove the scaleEffect() modifier from the button and comment out the animationAmount += 1 action part too, because we don’t want that to change any more, and move its animation modifier up to the circle inside the overlay:

.overlay(
    Circle()
        .stroke(Color.red)
        .scaleEffect()
        .opacity(Double(2 - animationAmount))
        .animation(
            Animation.easeOut(duration: 1)
                .repeatForever(autoreverses: false)
        )
)

I’ve switched autoreverses to false, but otherwise it’s the same animation.

Finally, add an onAppear() modifier to the button, which will set animationAmount to 2:

.onAppear {
    self.animationAmount = 2
}

Because the overlay circle uses that for a “repeat forever” animation without autoreversing, you’ll see the overlay circle scale up and fade out continuously.

Your finished code should look like this:

Button("Tap Me") {
    // self.animationAmount += 1
}
.padding(40)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
.overlay(
    Circle()
        .stroke(Color.red)
        .scaleEffect(animationAmount)
        .opacity(Double(2 - animationAmount))
        .animation(
            Animation.easeOut(duration: 1)
                .repeatForever(autoreverses: false)
        )
)
.onAppear {
    self.animationAmount = 2
}

Given how little work that involves, it creates a remarkably attractive effect!

Hacking with Swift is sponsored by RevenueCat

SPONSORED Building in-app subscriptions are hard. RevenueCat makes it simple. With their open source SDKs, you can painlessly implement subscriptions for your app in hours, not months.

Explore the docs to learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Snapthread is a casual video editor and slideshow maker that makes discovering, compiling and sharing your favorite memories effortless.

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.8/5