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):
// » 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:
// » 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:
// » 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:
// » 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.