【问题标题】:ObervableObject being init multiple time, and not refreshing my viewObervableObject 多次初始化,并且不刷新我的视图
【发布时间】:2020-02-18 10:32:49
【问题描述】:

我有这样的结构

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() 不刷新我的视图的原因

【问题讨论】:

  • Fernand,你所有的麻烦都源于你的模型依赖于这样一个事实,如果出现一些视图......即使你最终会找到一些“解决方案”,它也会非常脆弱!尽快改变你的方法。

标签: swift xcode swiftui-navigationlink swiftui


【解决方案1】:

这里有两个问题:

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

#1

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

换句话说,您几乎应该始终避免使用@ObservedObject var myObject = MyObservableObject() 之类的内容。

(注意,即使您使用@State var model = ListObj(),它也会每次都被实例化。但因为它是@State,SwiftUI 会在调用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 即可解决您当前的问题只要它是层次结构中的顶视图,就像您将它推入NavigationView 或展示它一样。

但是,这就是 @user3441734 的评论所在。您真正需要做的是将 model 的所有权保留在您的 View 之外的某个位置,因为在 #1 中进行了解释。

【讨论】:

  • 非常感谢,我在很多教程中都看到了 model = model(),没想到会这样。
【解决方案2】:

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

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

What is the difference between ObservedObject and StateObject in SwiftUI

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 2015-01-12
    • 2023-03-29
    • 2022-01-13
    • 1970-01-01
    • 2017-06-07
    • 2015-10-22
    相关资源
    最近更新 更多