SwiftUI doesn’t “redraw the screen”.
It diffs view trees.
If you don’t understand how SwiftUI decides what changed vs what stayed the same, you’ll see:
- unnecessary re-renders
- list animations breaking
- views flashing or resetting
- state jumping between rows
- performance degrading over time
This post explains how SwiftUI reconciles view trees, how diffing works, and how to write SwiftUI that updates only what needs to change.
🧠 The Core Idea: SwiftUI Is a Tree Diff Engine
Every time state changes, SwiftUI:
- Recomputes
body - Builds a new view tree
- Diffs it against the previous tree
- Updates only the differences
SwiftUI does not mutate views directly.
It replaces parts of the tree.
🌳 What Is a View Tree?
This code:
VStack {
Text("Title")
Button("Tap") { }
}
Becomes a tree like:
VStack
├─ Text
└─ Button
Every update builds a new tree.
Diffing decides which nodes are:
- reused
- updated
- replaced
- removed
🆔 Identity Is the Key to Diffing
SwiftUI matches nodes using identity.
Identity is determined by:
- view type
- position in hierarchy
- explicit
id()
If identity matches → node is reused
If identity differs → node is replaced
⚠️ The Most Common Diffing Bug
ForEach(items) { item in
Row(item: item)
}
If item.id:
- changes
- is derived
- is generated dynamically
SwiftUI cannot match rows correctly.
Result:
- wrong animations
- state jumps rows
- flickering
- performance issues
Always use stable IDs.
ForEach(items, id: \.id) { item in
Row(item: item)
}
🔥 Why id() Forces Reconciliation
Text(title)
.id(title)
When title changes:
- SwiftUI treats this as a new node
- old node is removed
- new node is inserted
That means:
- all state resets
- animations restart
- layout recalculates
This is not a bug — it’s explicit diff control.
🧱 Structural Changes vs Value Changes
Value change:
Text(count.description)
- same node
- only text updates
Structural change:
if count > 0 {
Text("Visible")
}
When condition flips:
- node is removed or inserted
- identity changes
- animations trigger
- state resets
Structural changes are expensive compared to value updates.
🧵 Conditional Views & Diffing
Bad pattern:
if loading {
ProgressView()
} else {
ContentView()
}
These are different trees.
Better:
ZStack {
ContentView()
if loading {
ProgressView()
}
}
This preserves identity and minimizes reconciliation.
📦 ViewModel Recreation & Diffing
MyView(viewModel: ViewModel()) // ❌
SwiftUI sees:
- new parameter
- new identity
- new subtree
Correct:
@StateObject var viewModel = ViewModel()
ViewModel identity stability = predictable diffing.
⚖️ Equatable Views & Diff Short-Circuiting
SwiftUI can skip updates if a view is Equatable.
struct Row: View, Equatable {
let model: Model
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.model.id == rhs.model.id &&
lhs.model.value == rhs.model.value
}
}
If equal:
- SwiftUI skips reconciliation
- no layout
- no redraw
Use for:
- heavy rows
- dashboards
- frequently updating parents
📐 Layout Is Re-Evaluated During Diffing
Even reused nodes:
- may re-layout
- may re-measure
- may re-render
Avoid:
- GeometryReader in lists
- deep nested stacks
- layout work inside
body
Efficient diffing depends on efficient layout.
🔄 Animation Is Diff-Driven
Animations happen when SwiftUI sees:
- insertion
- removal
- movement
- value interpolation
Bad identity → broken animations
Good identity → smooth transitions
If an animation looks wrong:
👉 the diffing model is confused.
🧪 Debugging Diffing Problems
Ask:
- Did identity change?
- Did the structure change?
- Did a conditional flip?
- Did a parent recreate?
- Did
id()change? - Did ordering change?
Diff bugs are deterministic — once you find identity issues, they disappear.
🧠 Mental Model Cheat Sheet
- SwiftUI builds trees
- Trees are diffed
- Identity controls reuse
- Structure changes cause reconciliation
- Value changes update in place
- Stable identity = performance + correct UI
🚀 Final Thoughts
SwiftUI is not slow.
Broken diffing makes it look slow.
Once you understand:
- identity
- reconciliation
- tree structure
- diff boundaries
You can build:
- large lists
- complex animations
- dynamic UIs
- scalable architectures
Top comments (0)