withAnimation: The Implicit Animation Transaction
withAnimation wraps state changes in a transaction, telling SwiftUI to tween affected layout automatically. Call it from button actions or view model methods to animate many views from one state flip.
WHY IT EXISTS: Before withAnimation, every animated change in SwiftUI required attaching an .animation modifier to a view. This scattered animation logic across the view tree and made it hard to trigger an animation from a view model or a network response. Apple introduced withAnimation to decouple the what from the how: you define which state changes should be animated, and the framework figures out which views are affected.
THE MENTAL MODEL: Think of withAnimation as a colored dye you pour into a river of state changes. Any view downstream that drinks from that specific state stream will change color smoothly. Views that drink from a different stream, or that already finished drawing before the dye arrived, stay unchanged. The dye is the animation transaction, and the river is your state update closure.
HOW IT WORKS: You call withAnimation(_:body:) and pass an Animation value like .easeInOut or .spring(). Inside the trailing closure, you mutate @State, @ObservedObject, or any other source of truth. SwiftUI creates an implicit animation transaction for that closure. When the body of any view re-evaluates and reads a value that changed inside the transaction, SwiftUI interpolates the presentation value from the old to the new using the specified animation curve. The transaction propagates through the view hierarchy but only affects properties that actually changed and were read during the current render pass.
WHEN TO USE IT: Use withAnimation when you want to animate a state change that originates outside the view tree, such as in a view model method, a button action, or a Combine pipeline completion handler. It shines when one state flip affects many views, because you write the animation once around the state change instead of attaching .animation to every dependent view. It is also the right tool when you want different state changes to use different animation curves in the same screen.
WHEN NOT TO USE IT: Do not use withAnimation when you need continuous animation driven by a gesture or a timer; reach for the Animatable protocol or phase animators instead. Avoid it when you want per-view control over which properties animate, because withAnimation is a blunt instrument that animates every eligible change in its scope. It also fails silently if the state mutation happens outside the closure, such as inside a DispatchQueue.main.async block or an await call, because the transaction does not cross suspension points or queue hops.
ONE CANONICAL EXAMPLE: Imagine a todo list where tapping a filter button switches between All, Active, and Completed. The view model holds var filter: Filter and publishes changes. Inside the button action, you write withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { viewModel.filter = .active }. Every list row that reads viewModel.filter to decide its opacity or offset will now slide and fade in unison. If you had instead set viewModel.filter = .active on the previous line and called withAnimation on an empty closure, nothing would move because the transaction wrapped no actual state change.
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.