Question 7
SwiftUI lists, identity, and expensive body work
Scrolling stutters in a SwiftUI List with many rows. What causes unnecessary view invalidation, and how do you structure rows for performance?
Answer outline
Remember the rule: stable identity, cheap rows, narrow updates.
Stable identity: SwiftUI decides what changed by comparing view identity. If rows use random ids, freshly-created UUID() values, or array indexes that shift when data reorders, SwiftUI treats old rows as new rows. That causes rebuilds, lost row state, and jumpy scrolling.
Cheap rows: body is not a setup method, it can run many times. Don’t decode JSON, resize full images, create formatters, or do expensive filtering inside a row body. Prepare data in the model, cache formatters, load images asynchronously, and downsample images before they hit the row.
Narrow updates: Keep each row focused on the small piece of data it needs. If one parent state change invalidates the whole list, split rows into smaller views or pass simpler values so SwiftUI can redraw less work.
Async lifecycle: Start side effects from .task / .task(id:) or an injected model, not from body. SwiftUI cancels .task when the view disappears and restarts .task(id:) when the id changes, which is usually a better fit than ad hoc onAppear network calls.
Principles
- Identity tells SwiftUI whether this is the same row.
- Body should describe UI, not perform heavy work.
- Invalidation should be as local as you can make it.
Use a real domain id that stays the same across reloads and reorders.
ForEach(items) { item in // Item.id is stable
RowView(item: item)
}
Follow-up angles
- Bad smell:
ForEach(items.indices)is fragile if the list can insert, delete, or reorder. - Images: use
AsyncImageor an image pipeline with downsampling; avoid full-size decode in row views. - Lifecycle: prefer
.task(id:)when loading depends on row identity; it gives cancellation semantics that plainonAppeardoes not encode as clearly. - Profiling: use Instruments SwiftUI / Time Profiler when guessing stops helping. Look for repeated
bodywork and broad invalidations.