Skip to content

Animating value changes

For visual changes that are triggered by a changing value in a View, there are three different approaches to animate the change:

.animation {}

a) Apply the animated view modifiers selectively inside a view-modifiying-block using .animation {} (preferred method, because it's most precise regarding what animation gets applied to what):

swift
// » SwiftUI Garden
// » https://swiftui-garden.com/Animations/Animating-value-changes

import SwiftUI

struct AnimatedValueContentBlockExample: View {
    @State var isLarge = false

    var body: some View {
        VStack {
            HStack {
                Rectangle()
                    .foregroundColor(Color.red)
                    .frame(width: 150, height: 150)
                    .clipShape(.rect(cornerRadius: 30))
                    .animation(.default) { content in
                        content
                            .scaleEffect(isLarge ? 1.0 : 0.4)
                    }
            }
            .frame(width: 180, height: 180)

            Button("Animate", systemImage: "play.fill") {
                self.isLarge.toggle()
            }
            .buttonStyle(.borderedProminent)
        }
    }
}

#Preview {
    AnimatedValueContentBlockExample()
}

.animation(value:)

b) Use the .animation() modifier to trigger an animation when a value changes:

swift
// » SwiftUI Garden
// » https://swiftui-garden.com/Animations/Animating-value-changes

import SwiftUI

struct AnimatedValueExample: View {
    @State var isLarge = false

    var body: some View {
        VStack {
            HStack {
                Rectangle()
                    .foregroundColor(Color.red)
                    .frame(width: 150, height: 150)
                    .clipShape(.rect(cornerRadius: 30))
                    .scaleEffect(isLarge ? 1.0 : 0.4)
                    .animation(.default, value: isLarge)
            }
            .frame(width: 180, height: 180)

            Button("Animate", systemImage: "play.fill") {
                self.isLarge.toggle()
            }
            .buttonStyle(.borderedProminent)
        }
    }
}

#Preview {
    AnimatedValueExample()
}

.animation(value:) Pitfall

Watch out for this potentially surprising behaviour: Inside the View that is modified by .animation(value:), all other changes that happen together with the value change in the same "view update cycle", will also be animated:

swift
// » SwiftUI Garden
// » https://swiftui-garden.com/Animations/Animating-value-changes

import SwiftUI

struct AnimatedValueGotchaExample: View {
    @State var isLeftLarge = false
    @State var isRightLarge = false

    var body: some View {
        VStack {
            HStack {
                Color.blue
                    .frame(width: 120, height: 120)
                    .scaleEffect(isLeftLarge ? 1.0 : 0.5)

                Color.blue
                    .frame(width: 120, height: 120)
                    .scaleEffect(isRightLarge ? 1.0 : 0.5)
            }
            .frame(width: 180, height: 180)

            Button("Animate", systemImage: "play.fill") {
                self.isLeftLarge.toggle()
                self.isRightLarge.toggle()
            }
            .buttonStyle(.borderedProminent)
        }
        // Gotcha: this will also animate the change triggered by
        // isRightLarge as the change happens in the same View update
        .animation(.default, value: isLeftLarge)
    }
}

#Preview {
    AnimatedValueGotchaExample()
}

withAnimation {}

c) Wrap the value change in a withAnimation {} block:

swift
// » SwiftUI Garden
// » https://swiftui-garden.com/Animations/Animating-value-changes

import SwiftUI

struct WithAnimationExample: View {
    @State var isLarge = false

    var body: some View {
        VStack {
            ZStack {
                Rectangle()
                    .fill(Color.red)
                    .frame(width: 150, height: 150)
                    .clipShape(.rect(cornerRadius: 30))
                    .scaleEffect(isLarge ? 1.0 : 0.4)
            }
            .frame(width: 180, height: 180)

            Button("Animate", systemImage: "play.fill") {
                withAnimation(.default) {
                    self.isLarge.toggle()
                }
            }
            .buttonStyle(.borderedProminent)
        }
    }
}

#Preview {
    WithAnimationExample()
}

Pitfalls

The animation modifiers have subtle pitfalls. The most conspicuous one for me is that .frame() is not animatable; one should use scaleEffect instead. This article by Ole Bergmann points out a few more pitfalls: When .animation animates more (or less) than it’s supposed to

Other Animations

For animations that are more than View changes based on value changes, see Controlled Animations. If it's a View appearing or disappearing, see View Transitions.