5

我有这样的结构

contentView {
    navigationView{
     foreach {
        NavigationLink(ViewA(id: id))
     }
    }
}

///其中视图A包含一个请求触发器在视图中出现

struct ViewA: View {

    @State var filterString: String = ""

    var id: String!
    @ObservedObject var model: ListObj = ListObj()

    init(id: String) {
        self.id = id
    }

    var body: some View {
        VStack {
            SearchBarView(searchText: $filterString)
            List {
                ForEach(model.items.filter({ filterString.isEmpty || $0.id.contains(filterString) || $0.name.contains(filterString)  }), id: \.id) { item in
                    NavigationLink(destination: ViewB(id: item.id)) {
                        VStack {
                            Text("\(item.name) ")
                        }
                    }
                }
            }

        }
        .onAppear {
            self.model.getListObj(id: self.id) //api request, fill data and call objectWillChange.send()
        }

    }

}

}

ViewB 和 ViewB 有相同的代码,接收 id,存储,请求 api 来收集数据。

但是 viewB 列表没有被刷新。我也注意到了viewB的

@ObservedObject var model: model = model()

是多次实例化的

调试时,我发现每个 navigationLink 实例都是它的目的地,甚至在它被触发之前。这通常不是问题,

但就我而言,我觉得 ViewB 模型被实例化了 2 次,而我的 onApear 调用了错误的模型,这是 self.objectWillChange.send() 不刷新视图的原因

4

2 回答 2

11

这里有两个问题:

  1. SwiftUI 使用的值类型在每次通过时都会一遍又一遍地初始化body
  2. 与#1相关,NavigationLink并不懒惰。

#1

ListObj每次调用时都会实例化一个 new ViewA.init(...)ObservedObject SwiftUI@State在整个屏幕生命周期中为您仔细跟踪它的方式不同。SwiftUI 假设一个对象的最终所有权@ObservedObject存在于它所使用的某个级别之上View

换句话说,你应该几乎总是避免像@ObservedObject var myObject = MyObservableObject().

(注意,即使你这样做@State var model = ListObj()了,它每次都会被实例化。但因为它是@StateSwiftUI,所以在调用之前会用原始实例替换新实例body。)

#2

除此之外,NavigationLink不偷懒。每次实例化时,NavigationLink您都会传递一个新实例ViewA化的ListObj.

因此,对于初学者来说,您可以做的一件事是LazyView延迟实例化,直到NavigationLink.destination.body实际被调用:

// Use this to delay instantiation when using `NavigationLink`, etc...
struct LazyView<Content: View>: View {
    var content: () -> Content
    var body: some View {
        self.content()
    }
}

现在你可以做NavigationLink(destination: LazyView { ViewA() }),并且实例化ViewA将被推迟到destination实际显示出来。

只要它是层次结构中的顶视图,只需使用即可LazyView解决您当前的问题,就像您将它推入 a或呈现它一样。NavigationView

但是,这就是@user3441734 的评论所在。由于#1 中的解释,您真正需要做的是保留model您之外某个地方的所有权。View

于 2020-02-19T07:25:14.970 回答
2

如果您@ObservedObject被多次初始化,那是因为对象的所有者在每次状态更改时都会刷新并重新创建。@StateObject如果您的应用是 iOS 14 及更高版本,请尝试使用。它可以防止在视图刷新时重新创建对象。 https://developer.apple.com/documentation/swiftui/stateobject

当视图创建自己的 @ObservedObject 实例时,每次丢弃和重绘视图时都会重新创建它。相反,@State 变量将在重绘视图时保持其值。@StateObject 是 @ObservedObject 和 @State 的组合 - ViewModel 的实例将被保留并重用,即使在视图被丢弃和重绘后也是如此

SwiftUI 中 ObservedObject 和 StateObject 有什么区别

于 2021-03-03T18:48:27.970 回答