列表行中的SwiftUI@StateObject



SwiftUI似乎不会为列表行持久化@StateObjects,当该行嵌入堆栈或NavigationLink之类的容器中时。这里有一个例子:

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

当您向下滚动列表时,您会看到每出现一个新行都记录了"INIT"。但向上滚动,您会看到每一行都会再次记录"INIT",即使它们已经出现。

现在移除NavigationLink:

List(0..<40) { _ in
ListRow()
}

@StateObject的行为与预期一致:每行正好有一个"INIT",没有重复。ObservableObject在视图刷新期间保持不变。

SwiftUI在持久化@StateObjects时遵循什么规则?在这个例子中,MyObject可能正在存储重要的状态信息或下载远程资产,那么我们如何确保每行只发生一次(当与NavigationLink等组合时(?

以下是文档中关于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. 

List实际上并没有创建新的行实例,而是重用之前创建的并离开屏幕。然而,NavigationLink每次都会为标签创建新的实例,所以您可以看到这一点。

您的案例可能的解决方案是将NavigationLink移动到ListRow:内部

struct ListView: View {
var body: some View {
List(0..<40) { _ in
ListRow()
}
}
}

struct ListRow: View {
@StateObject var obj = MyObject()
var body: some View {
NavigationLink(destination: Text("Dest")) {     // << here !!
Text("Row")
}
}
}

如果你想在没有导航的地方重用ListRow,你甚至可以将它们分开

struct LinkListRow: View {
@StateObject var obj = MyObject()
var body: some View {
NavigationLink(destination: Text("Dest")) {
ListRow(obj: obj)
}
}
}

最新更新