I am experimenting with SwiftUI and noticed unexpected behavior when using a custom view inside a List. Here’s the simplified code:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List(0..<1000, id: \.self) { index in
let _ = print(index)
Text("\(index)") // On initial load ~18 cells are created
// Cell(index: index) // On initial load 1000 cells are created
}
}
}
}
struct Cell: View {
let index: Int
var body: some View {
Text("\(index)")
}
}
#Preview {
ContentView()
}
Observed Behavior:
When using Text("\(index)")
directly, only around 18 cells are created on the initial load (as expected due to the lazy rendering of SwiftUI's List).
When switching to the custom view Cell(index: index)
, all 1000 cells are created immediately, significantly affecting performance.
Question:
Why does using a custom view inside the List cause all cells to be rendered eagerly, and how can I fix this while still using the custom view?
Note, NavigationView
is deprecated use NavigationStack
instead.
List
are lazy by default, but your side effect
let _ = print(item)
is responsible for showing all the index
, it is eagerly evaluated.
Remove it to ...fix this while still using the custom view?
To see this in action, remove let _ = print(item)
from the loop, and
instead add
.onAppear { print(index) }
to your Cell
, such as
struct Cell: View {
let index: Int
var body: some View {
Text("\(index)")
.onAppear {
print(index) // <-- here
}
}
}
The eager evaluation (of print) does not happens when Text
is used, because
Text
is a built-in view in SwiftUI that has knowledge and optimizations for handling primitive views. The eager evaluation is not done because it is not a custom view.