Creating Your First Animation

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In SwiftUI, your user interface is a direct function of your app’s state. When a value in your state changes, SwiftUI automatically and efficiently re-renders the parts of your UI that depend on that value. Because SwiftUI knows the “before” and “after” states, it can seamlessly and automatically calculate all the in-between steps to create a fluid animation.

The modern and recommended way to create an animation is to wrap the state change itself—the cause of the UI update—in a withAnimation block. This paradigm shift is powerful because it attaches the animation to the action, not the view. It makes your code’s intent clearer: “When this happens, animate the consequences.”

In the example below, we will schedule a series of state changes to happen over time using DispatchQueue.

A Quick Intro to DispatchQueue

Before looking at the code, let’s briefly touch on DispatchQueue. At this stage, you can think of it as a manager for your app’s to-do list.

// MARK: - 1. Basic Animation: withAnimation
struct BasicAnimationView: View {
    // Data for our animation
    struct AnimationData: Equatable {
        var color: Color
        var offset: CGSize
    }

    private let animationDataPoints: [AnimationData] = [
        .init(color: .green, offset: .init(width: -100, height: -100)),
        .init(color: .blue, offset: .init(width: 100, height: -100)),
        .init(color: .red, offset: .init(width: 100, height: 100)),
        .init(color: .orange, offset: .init(width: -100, height: 100)),
        .init(color: .green, offset: .init(width: -100, height: -100))
    ]

    @State private var currentDataPoint = AnimationData(color: .green, offset: .init(width: -100, height: -100))

    var body: some View {
        VStack {
            Text("withAnimation")
                .font(.largeTitle)
                .fontWeight(.bold)
                .padding(.bottom, 50)

            Circle()
                .foregroundStyle(currentDataPoint.color)
                .frame(width: 80, height: 80)
                .offset(currentDataPoint.offset)
            
            Spacer()
        }
        .onAppear(perform: cycleAnimationData)
    }

    func cycleAnimationData() {
        for (index, data) in animationDataPoints.enumerated() {
            DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(index * 2)) {
                withAnimation(.spring(duration: 0.8, bounce: 0.5)) {
                    currentDataPoint = data
                }
            }
        }
    }
}

Combining Animations

The power of state-driven animation becomes truly clear when you modify multiple view properties with a single state change. Imagine an app for booking tours where tapping a tour’s thumbnail image makes it expand to fill the screen. We want this interaction to feel dynamic and satisfying.

// MARK: - 2. Combined Animation: One State, Many Effects
struct CombinedAnimationView: View {
    @State private var zoomed = false

    var body: some View {
        VStack {
            Text("Combined Animations")
                .font(.largeTitle)
                .fontWeight(.bold)
                .padding(.bottom, 50)

            Image(systemName: "photo.artframe")
                .font(.system(size: 100))
                .foregroundStyle(.white, zoomed ? .cyan : .blue)
                .clipShape(RoundedRectangle(cornerRadius: zoomed ? 40 : 100))
                .scaleEffect(zoomed ? 2.0 : 1.0)
                .rotationEffect(zoomed ? .degrees(0) : .degrees(360))
                .shadow(radius: 10)
                .onTapGesture {
                    withAnimation(.spring(duration: 0.7, bounce: 0.5)) {
                        zoomed.toggle()
                    }
                }
            
            Text("Tap the image!")
                .padding(.top)
            
            Spacer()
        }
    }
}

Built-in Animations

.default

withAnimation(.default) { ... }
Animation.easeInOut(duration: 0.35)
withAnimation(.linear(duration: 1.0)) { ... }
withAnimation(.easeIn(duration: 1.0)) { ... }
withAnimation(.easeOut(duration: 1.0)) { ... }
withAnimation(.easeInOut(duration: 1.0)) { ... }
withAnimation(.spring(response: 0.5, dampingFraction: 0.6)) { ... }
withAnimation(.interpolatingSpring(stiffness: 100, damping: 5)) { ... }

Custom Timing Curve (Bezier)

.timingCurve (via Animation.timingCurve)

withAnimation(Animation.timingCurve(0.25, 0.1, 0.25, 1, duration: 1.0)) { ... }

Repeating Animation

Any of the above can be combined with .repeatForever() or repeatCount().

.withAnimation(.linear(duration: 1).repeatForever(autoreverses: true)) { ... }

Summary

Here is a handy chart showing all of the built in animation types and where to use them.

Riev tov Dozhvojhail Edaracoud Ygyu Cuyucr, mpahling vibl Hapznihh ppuiy . wakoim Mofelus Ufo Fjiarc eeja ol iuz .femaivq Cerootn, ukxvun Nqurqj ddex . oisoUh Ugeld, cojithiitovy iqahamxt Awst zgis .iapiUof Kexjecd, zzuwxij calean Yghasjs, maewqx .fwqesd Hciwace xoxqef sikizq Vevqik Muroik ziqva .sizawcBebbe Wmordoqauym, nrabiy Wdoh > kobc > pvob .oegeOsEim Kipros sjeqzmivc/disdiww Tyfkigb‑hgcfi khzilh .ubyoysihagahmWqhanb

See forum comments
Download course materials from Github
Previous: Introduction to SwiftUI Animation Next: Advanced Animation Techniques