简体   繁体   中英

SwiftUI @StateObject inside List rows

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)?

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)
       }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM