Swift and SwiftUI tutorials for Swift Developers

.animation() vs withAnimation() in SwiftUI

In the vast universe of Swift programming, the ability to bring an interface to life separates a functional application from an extraordinary user experience. For an iOS Developer, understanding how and when to move pixels on the screen is a critical skill.

Since the arrival of SwiftUI, Apple has gifted us a declarative animation system that is both incredibly powerful and a frequent source of confusion. The most recurring question in Xcode forums and StackOverflow is clear: What is the real difference between the .animation() modifier and the global withAnimation() function?

In this comprehensive tutorial, we will break down SwiftUI’s rendering architecture, explore the technical similarities and differences between these two methods, and define best practices for developing applications on iOS, macOS, and watchOS.

The Philosophy of Motion in SwiftUI

Before writing a single line of code in Xcode, we must understand the paradigm shift. Unlike UIKit, where animation was imperative (“move this view from X to Y”), SwiftUI works with states. Animation is not the movement itself, but the interpolation of data over time between two states of the view.

Both .animation() and withAnimation() have the same ultimate goal: to communicate to the rendering system that a state change should not be instantaneous. However, “how” and “where” that instruction is applied drastically changes your app’s behavior.

1. Implicit Animation: The .animation() Modifier

Implicit animation is declared as a property of the view itself. By using the .animation() modifier, you are establishing a contract: “Whenever the observed value changes, apply this animation curve to this view.”

The Evolution: From Chaos to Control

In early versions of SwiftUI, it was common to see .animation(.default). This is now considered a bad practice (and is deprecated in recent iOS versions) because it animated any change in the view, even when the view appeared for the first time or when an unrelated property changed. The modern iOS Developer uses the value-bound variant: .animation(_:value:).

Let’s look at a practical example of how to correctly implement implicit animation in Swift:

import SwiftUI

struct ImplicitAnimationExample: View {
    @State private var isActive: Bool = false

    var body: some View {
        VStack(spacing: 20) {
            Text("Implicit Animation")
                .font(.headline)
            
            RoundedRectangle(cornerRadius: 20)
                .fill(isActive ? Color.blue : Color.gray)
                .frame(width: isActive ? 300 : 100, height: 100)
                .overlay(Text(isActive ? "Active" : "Inactive").foregroundColor(.white))
                // HERE: We apply the animation linked to the 'isActive' value.
                // It will only animate when this specific variable changes.
                .animation(.spring(response: 0.5, dampingFraction: 0.6), value: isActive)
            
            Button("Change State") {
                // Note: We don't need withAnimation here
                isActive.toggle()
            }
            .buttonStyle(.borderedProminent)
        }
    }
}

What is happening here? The modifier observes the isActive variable. When we press the button, the state changes. SwiftUI detects that the value linked to the modifier has mutated and automatically calculates the intermediate frames for color, width, and text.

Advantages of Implicit Animation

  • Localization: You define the animation right where you define the view. It’s easy to read.
  • Granularity: You can have three properties on the same view (opacity, position, color) animated with different curves if you apply multiple .animation() modifiers pointing to different values.
  • Automation: No matter what causes the state change (a timer, an API response, or a gesture), the view will always animate smoothly.

2. Explicit Animation: The withAnimation() Function

While implicit animation focuses on the “View”, explicit animation focuses on the “Action”. withAnimation() is a global function that wraps a state change. It tells the system: “Everything that changes as a result of this block of code must be animated.”

This approach is fundamental for handling view entry and exit transitions, something that often fails with implicit animations. In SwiftUI, this creates what we call a Transaction.

import SwiftUI

struct ExplicitAnimationExample: View {
    @State private var showDetails: Bool = false

    var body: some View {
        VStack {
            Button(action: {
                // HERE: Explicit Animation
                // We define the animation curve for this specific action
                withAnimation(.easeInOut(duration: 0.6)) {
                    showDetails.toggle()
                }
            }) {
                HStack {
                    Text("View Details")
                    Image(systemName: "chevron.right")
                        .rotationEffect(.degrees(showDetails ? 90 : 0))
                }
            }

            if showDetails {
                Text("These are the hidden details appearing with a smooth transition.")
                    .padding()
                    .background(Color.yellow.opacity(0.3))
                    .cornerRadius(10)
                    .transition(.move(edge: .top).combined(with: .opacity))
            }
        }
    }
}

The Power of Transitions

The example above illustrates the strength of withAnimation(). If you tried to use .animation() on the conditional view if showDetails, you would often see the view appear smoothly but disappear abruptly. This happens because when the condition is false, the view is removed from the hierarchy immediately. withAnimation() coordinates the transaction to ensure the view has time to execute its exit transition before being destroyed.

Technical Comparison: Similarities and Differences

To optimize your workflow in Xcode, I have compiled this comparison table:

Feature .animation() (Implicit) withAnimation() (Explicit)
Focus View-Oriented (Declarative) Action-Oriented (Imperative)
Scope Affects only the modified view and its children. Affects the entire view tree dependent on the state.
Transitions (Insert/Remove) Limited / Error-prone. The recommended and robust method.
Priority Can be overridden by explicit transactions. Defines the base transaction.

Advanced Scenarios in Swift Programming

A good iOS Developer not only knows the tools but also their exceptions. Here we explore complex use cases.

Overriding Animations

What happens if you use withAnimation for everything, but you want a specific part of the interface to change instantly? You can override the transaction using .animation(nil).

struct OverrideAnimationExample: View {
    @State private var expand = false

    var body: some View {
        VStack {
            // This rectangle will obey the button's withAnimation
            Rectangle()
                .fill(Color.blue)
                .frame(width: expand ? 200 : 50, height: 50)
            
            // This rectangle will ignore the explicit animation and jump in size
            Rectangle()
                .fill(Color.red)
                .frame(width: expand ? 200 : 50, height: 50)
                // HERE: Animation Override
                .animation(nil, value: expand) 
            
            Button("Expand") {
                withAnimation(.slow) {
                    expand.toggle()
                }
            }
        }
    }
}

Animating Bindings: The Hybrid

There is a third path often forgotten in SwiftUI tutorials: animating the Binding itself. This is extremely useful when you pass a state to a subview (like a Toggle) and want the change of that toggle to animate the parent view, without cluttering the child view’s logic.

struct BindingAnimationView: View {
    @State private var isBlurred = false

    var body: some View {
        VStack {
            Image("landscape") // Make sure you have an image asset
                .resizable()
                .blur(radius: isBlurred ? 20 : 0)
                .frame(width: 300, height: 200)

            // HERE: Animation on the Binding
            // The Toggle doesn't know about animations, but the value change
            // injects the animation into the view tree.
            Toggle("Blur", isOn: $isBlurred.animation(.easeInOut))
                .padding()
                .frame(width: 200)
        }
    }
}

Optimization and Performance in iOS, macOS, and watchOS

Performance is key. Although Apple devices are powerful, abusing .animation() can cause frame drops, especially in long lists.

The List Problem: If you have a LazyVStack or List and apply implicit animation to the cells, cell recycling during scrolling can be interpreted as a “state change,” triggering unwanted animations (jelly effect or flickering).

The Solution: For complex list cells, prefer using withAnimation triggered only by user interaction (tap), avoiding animations during the cell loading lifecycle.

Conclusion: Which One Should I Use?

To summarize this Swift programming guide:

  1. Use .animation(_:value:) for decorative visual effects, micro-interactions within an isolated component, and when the state change does not involve the appearance/disappearance of views.
  2. Use withAnimation() for navigation logic, drastic layout changes, transitions (if/else), and when multiple separate views must move in unison.

Mastering these two tools in Xcode will allow you to create fluid and professional interfaces. Animation is not just aesthetics; it is the visual narrative of your application.

If you have any questions about this article, please contact me and I will be happy to help you 🙂. You can contact me on my X profile or on my Instagram profile.

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

Transitions in SwiftUI

Next Article

Build a website with Swift

Related Posts