Reputation: 3587
SwiftUI doesn't seem to persist @StateObjects
for list rows, when the row is embedded inside a container like a stack or NavigationLink
. Here's an example:
class MyObject: ObservableObject {
init() { print("INIT") }
}
struct ListView: View {
var body: some View {
List(0..<40) { _ in
NavigationLink(destination: Text("Dest")) {
ListRow()
}
}
}
}
struct ListRow: View {
@StateObject var obj = MyObject()
var body: some View {
Text("Row")
}
}
As you scroll down the list, you see "INIT"
logged for each new row that appears. But scroll back up, and you see "INIT"
logged again for every row - even though they've already appeared.
Now remove the NavigationLink
:
List(0..<40) { _ in
ListRow()
}
and the @StateObject
behaves as expected: exactly one "INIT"
for every row, with no repeats. The ObservableObject
is persisted across view refreshes.
What rules does SwiftUI follow when persisting @StateObjects
? In this example MyObject
might be storing important state information or downloading remote assets - so how do we ensure it only happens once for each row (when combined with NavigationLink
, etc)?
Upvotes: 3
Views: 1099
Reputation: 258413
Here is what documentation says about StateObject
:
/// @StateObject var model = DataModel()
///
/// SwiftUI creates a new instance of the object only once for each instance of
/// the structure that declares the object.
and List
really does not create new instance of row, but reuses created before and went offscreen. However NavigationLink
creates new instance for label every time, so you see this.
Possible solution for your case is to move NavigationLink
inside ListRow
:
struct ListView: View {
var body: some View {
List(0..<40) { _ in
ListRow()
}
}
}
and
struct ListRow: View {
@StateObject var obj = MyObject()
var body: some View {
NavigationLink(destination: Text("Dest")) { // << here !!
Text("Row")
}
}
}
You can even separate them if, say, you want to reuse ListRow
somewhere without navigation
struct LinkListRow: View {
@StateObject var obj = MyObject()
var body: some View {
NavigationLink(destination: Text("Dest")) {
ListRow(obj: obj)
}
}
}
Upvotes: 3