Animations & Transitions
Why SwiftUI animation is easy
Because SwiftUI already knows when your state changes, you mostly just say “animate this change” and it computes all the in-between frames for you. There is no manual frame-by-frame work.
Implicit animation with .animation
struct ExpandBox: View {
@State private var expanded = false
var body: some View {
VStack {
RoundedRectangle(cornerRadius: 12)
.fill(.purple)
.frame(height: expanded ? 200 : 80)
.animation(.spring(), value: expanded) // animate this property
Button("Toggle") { expanded.toggle() }
}
.padding()
}
}
.animation(_, value:) animates whenever the given value changes — a clean, targeted approach.
Explicit animation with withAnimation
To animate the change itself (rather than attach to a view), wrap the state change in withAnimation:
Button("Like") {
withAnimation(.easeInOut(duration: 0.3)) {
liked.toggle()
}
}
Transitions: appear and disappear
When a view is added or removed, a transition controls how it enters/exits. Combine with conditional content:
if showDetail {
DetailPanel()
.transition(.move(edge: .bottom).combined(with: .opacity))
}
// trigger inside a withAnimation { showDetail.toggle() }
Animation curves
.easeInOut— smooth start and stop (good default)..spring()— natural, bouncy motion..linear— constant speed (good for continuous things).
Looping and continuous animation
@State private var pulse = false
Circle().fill(.purple)
.scaleEffect(pulse ? 1.2 : 1)
.animation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true), value: pulse)
.onAppear { pulse = true }
Common mistakes
- Animating everything — subtle motion feels premium; too much feels chaotic.
- Forgetting the
value:parameter so the animation doesn’t trigger. - Animating layout-heavy changes that cause jank — keep animations light.
Summary: Use.animation(_, value:)for property changes,withAnimationfor state changes, and.transitionfor views appearing/disappearing. Choose a curve (.spring,.easeInOut) and keep motion subtle.