SwiftUI.cc
Fix
Fix

SwiftUI Animation Not Triggering on First Render: Causes & Fix

SwiftUI animations may not run on initial view appearance, leading to unexpected UI behavior.

5 min readUpdated 2026-06

The Problem

A common issue in SwiftUI is when an animation does not play on the first render of a view. For example, trying to animate a view's scale or opacity upon appearance may not behave as expected:

// ❌ Wrong
struct ContentView: View {
    @State private var scale: CGFloat = 0.1

    var body: some View {
        Circle()
            .frame(width: 100, height: 100)
            .scaleEffect(scale)
            .onAppear {
                withAnimation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0.6)) {
                    scale = 1.0
                }
            }
    }
}

In this code, the scaleEffect animation might not play the first time the view appears.

Root Cause

SwiftUI's animation system relies on the concept of value-driven state changes. Animations are triggered when a @State, @Binding, or @ObservedObject changes, and the view updates in response. However, the initial rendering of a view is not considered a change in state — it's the first render. Therefore, even if you call withAnimation inside onAppear, the animation might not run as expected because SwiftUI doesn't have a prior value to animate from.

Additionally, SwiftUI may optimize out animations on initial render if the view hasn't been fully laid out or rendered yet.

The Fix

To ensure animations trigger on the first render, use the .animation() modifier with a binding to a state variable that changes after the view appears. This ensures the animation is tied to a state change that SwiftUI can track:

// ✅ Fixed
struct ContentView: View {
    @State private var scale: CGFloat = 0.1
    @State private var hasAppeared = false

    var body: some View {
        Circle()
            .frame(width: 100, height: 100)
            .scaleEffect(scale)
            .animation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0.6), value: hasAppeared)
            .onAppear {
                DispatchQueue.main.async {
                    self.hasAppeared = true
                    scale = 1.0
                }
            }
    }
}

By introducing hasAppeared and using it as the value: parameter in .animation(), we ensure the animation is triggered only after the view appears and the state changes. Using DispatchQueue.main.async delays the change slightly until after the initial render completes.

Alternative Approaches

Use onChange with a Bool flag

You can also use a @State boolean flag and trigger the animation inside an onChange block:

struct ContentView: View {
    @State private var scale: CGFloat = 0.1
    @State private var shouldAnimate = false

    var body: some View {
        Circle()
            .frame(width: 100, height: 100)
            .scaleEffect(scale)
            .onChange(of: shouldAnimate) { _ in
                withAnimation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0.6)) {
                    scale = 1.0
                }
            }
            .onAppear {
                shouldAnimate = true
            }
    }
}

This approach decouples the animation trigger from the direct state change and allows more control over when the animation runs.

Use task modifier (iOS 15+)

If targeting iOS 15+, you can use the task modifier to perform async setup:

.task {
    await MainActor.run {
        withAnimation(.spring(...)) {
            scale = 1.0
        }
    }
}

Affected iOS Versions

This behavior is present in iOS 13 through iOS 16. While SwiftUI's animation system has improved over time, especially with NavigationStack and task, the core issue of animations not triggering on first render persists unless explicitly handled.