Reputation: 370
Given a LazyHStack
inside a ScrollView
, child views are generally re-evaluated only as they come on screen, or if their underlying state changes. However, if the child views contain a conditional statement, this behavior breaks down. When the underlying state changes, all views that have been displayed so far are re-evaluated, even those that are well off screen. Why exactly does this happen, and are there any good workarounds to prevent this?
I have included some basic example code below, to be run in a Playground. To see the issue I'm talking about, do the following:
Text
view under "This is fine". Run the code again.struct ChildView: View {
let s: String
var body: some View {
// This will cause unnecessary re-evaluation
// if #available(iOS 16, *) {
// Text(s)
// .draggable("test")
// } else {
// Text(s)
// }
// This is fine
// Text(s)
}
}
struct ParentView: View {
@State private var items = Array(0...100)
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach(items, id: \.self) { n in
let _ = print("Evaluating \(n)")
ChildView(s: "Num \(n)")
}
}
}
Button("Mutate collection") {
items[0] = Int.random(in: 0...100)
}
.frame(width: 200, height: 500)
}
}
PlaygroundPage.current.setLiveView(ParentView())
Upvotes: 1
Views: 111
Reputation: 30575
Make the number of views per row constant, explained here
https://developer.apple.com/wwdc23/10160?time=1087
The basic equation to think about is that the row count resulting from a ForEach in a List is equal to the number of elements multiplied by the number of views produced for each element. You need to ensure the number of views per element is a constant, or SwiftUI has to build the views in addition to the identifiers in order to identify the rows.
So I suppose the runtime if makes it think it’s not constant.
Upvotes: 1
Reputation: 698
I don't know why it happens, but the solution that I found was encapsulating the mutating view in a view that which it won't affect it:
struct ChildView: View {
let s: String
var body: some View {
ZStack {
if #available(iOS 16, *) {
Text(s)
.draggable("test")
} else {
Text(s)
}
}
}
}
Upvotes: 1