TL;DR: Implicit animation (.animation) is bound to a view and works passively, defining “how to move if a change happens.” Explicit animation (withAnimation) wraps the state change logic and acts proactively, defining “every change caused by this specific update should be animated.”
SwiftUI provides two distinct mechanisms for animation: implicit and explicit. Beginners often confuse the priority and scope of these two, leading to lost animations or unexpected visual glitches. This article breaks down the core differences and best use cases for each.
Implicit Animation
Implicit animation is declared on a view using modifiers (like .animation). It acts like a property, telling the view system: “Whenever the monitored value changes, animate the transition using this specific curve.”
Key Characteristics:
- Proximity Rule: An implicit animation on a child view overrides animation settings from a parent view.
- Automatic Propagation: It propagates down the view hierarchy until it is intercepted by a deeper animation modifier.
struct ImplicitAnimationDemo: View {
@State private var isActive = false
var body: some View {
VStack {
VStack {
Text("Hello") // Defines .smooth itself; highest priority
.offset(x: isActive ? 200 : 0)
.animation(.smooth, value: isActive)
Text("World") // Inherits .linear from the parent VStack
.offset(x: isActive ? 200 : 0)
}
// Parent container defines .linear
.animation(.linear.speed(3), value: isActive)
// No animation defined, nor inherited (unless a Transaction is involved)
Text("No Animation")
.offset(x: isActive ? 200 : 0)
Toggle("Active", isOn: $isActive)
}
}
}
Explicit Animation
Explicit animation is imperative. It is triggered via withAnimation or a withTransaction closure. It tells the system: “For every state change happening inside this closure, apply an animation to the resulting view updates.”
Key Characteristics:
- Global Coverage: It provides a “default animation” for all views affected by the state change.
- Priority: If a view does not have an implicit animation, it uses the explicit animation. If a view does have an implicit animation, the implicit animation usually overrides the explicit one.
struct ExplicitAnimationDemo: View {
@State private var isActive = false
var body: some View {
VStack {
// ... (Same Hello/World structure as above) ...
// Has no implicit animation modifier
Text("Default Spring")
.offset(x: isActive ? 200 : 0)
Button("Toggle") {
// Explicit animation: All affected views without their own opinion use .spring
withAnimation(.spring) {
isActive.toggle()
}
}
}
}
}
In this example, when the button is clicked:
Text("Hello")still uses its own.smooth(Implicit overrides Explicit).Text("Default Spring")has no implicit animation, so it obeyswithAnimation(.spring).
Core Differences Summary
| Feature | Implicit Animation (.animation) | Explicit Animation (withAnimation) |
|---|---|---|
| Definition Location | View Hierarchy (View Modifier) | Logic Level (State Change) |
| Scope | Restricted to the modified view and its children | All views affected by the state inside the closure |
| Priority | High (Can override explicit animation) | Low (Acts as a default fallback) |
| Use Case | Fine-grained control for specific views | Global state changes, unified transitions, List operations |
Advanced Tip: Disabling Animations
Sometimes you need to force a specific view not to animate within the context of an explicit animation. You can achieve this using a Transaction.
// Forcefully disable animation
var transaction = Transaction(animation: .none)
transaction.disablesAnimations = true
withTransaction(transaction) {
isActive.toggle()
}
Or disable it at the view level:
Text("No Animation")
.animation(nil, value: isActive) // Forcefully set to nil
Modern Best Practices (Swift 6)
As SwiftUI evolves, adhere to these standards to avoid warnings and undefined behavior:
-
Must Bind a Value: ❌ Deprecated:
.animation(.spring())✅ Recommended:.animation(.spring, value: isActive)Reason: The old API caused performance issues and bugs where unrelated state changes in the view tree could trigger animations. -
Scoped Animations (iOS 17+): Use
.animation(_:body:)to precisely control the scope of an animation without affecting child views.
Text("Title")
.animation(.default) { content in
content.scaleEffect(isActive ? 1.2 : 1.0)
}