Asked 1 month ago by NebularStargazer487
Why does a custom view in a SwiftUI List trigger eager cell rendering and how can I fix it?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by NebularStargazer487
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I'm experimenting with SwiftUI and noticed unexpected behavior when using a custom view inside a List. In the following simplified code, using a direct Text view renders only about 18 cells on initial load due to SwiftUI's lazy loading, while switching to a custom Cell view results in all 1000 items being created immediately, which affects performance.
SWIFTimport 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:
• Using Text("\(index)")
directly leads to lazy rendering of around 18 cells on initial load.
• Using Cell(index: index)
eagerly creates all 1000 cells immediately.
Question:
Why does embedding a custom view cause this eager rendering, and how can I adjust my code to maintain lazy loading while still using a custom view?
Comments 0
•Answers 2
•Views 59
Answer by TychoAI Agent • 1 month ago
When you add a custom view in the List’s closure, the extra code (even something as simple as a print statement) or the structure of the view builder can break the lazy behavior. In your example the line
let _ = print(index)
forces the closure to execute completely for each element when the List is being built. This in turn causes SwiftUI to construct every Cell immediately rather than lazily when they’re about to appear on screen.
To fix this while still using your custom view you have a couple of options:
• Remove the debug side effect (or move it inside the custom view’s onAppear or initializer so that it doesn’t interfere with the closure’s laziness).
For example, you can change your List to simply:
List(0..<1000, id: .self) { index in
Cell(index: index)
}
and if you need to log when a cell becomes visible, add an .onAppear modifier inside Cell:
struct Cell: View {
let index: Int
var body: some View {
Text("(index)")
.onAppear {
print("Appeared: (index)")
}
}
}
• Alternatively, wrap the cell’s contents inside a Group so that the closure returns a single view without the extra binding, preserving the lazy loading.
In short, extra statements or bindings in the List’s view builder closure may force SwiftUI to compute all cells immediately. Moving side effects into the view (or removing them) restores the laziness and greatly improves performance.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by StarlitRanger318 • 1 month ago
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
SWIFT.onAppear { print(index) }
to your Cell
, such as
SWIFTstruct 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.
No comments yet.
No comments yet.