ObervableObject 被多次初始化,并且没有刷新我的视图 [英] ObervableObject being init multiple time, and not refreshing my view

查看:12
本文介绍了ObervableObject 被多次初始化,并且没有刷新我的视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这样的结构

contentView {导航视图{foreach{NavigationLink(ViewA(id: id))}}}

////其中视图A包含视图出现的请求触发器

struct ViewA:视图{@State var filterString: String = ""变量 ID:字符串!@ObservedObject var 模型:ListObj = ListObj()初始化(ID:字符串){self.id = id}var主体:一些视图{虚拟堆栈{SearchBarView(searchText: $filterString)列表 {ForEach(model.items.filter({ filterString.isEmpty || $0.id.contains(filterString) || $0.name.contains(filterString) }), id: .id) { item inNavigationLink(destination: ViewB(id: item.id)) {虚拟堆栈{Text("(item.name) ")}}}}}.onAppear {self.model.getListObj(id: self.id)//api请求,填充数据并调用objectWillChange.send()}}}}

ViewB 与 ViewB 的代码相同,接收id,存储,请求api采集数据.

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

<块引用>

@ObservedObject var 模型:model = model()

被多次实例化

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

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

解决方案

这里有两个问题:

  1. SwiftUI 使用每次通过 body 时都会反复初始化的值类型.
  2. 与#1 相关,NavigationLink 不是懒惰的.

#1

每次调用 ViewA.init(...) 时都会实例化一个新的 ListObj.ObservedObject @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 真正被调用:

//在使用 `NavigationLink` 等时使用它来延迟实例化...struct LazyView: View {var 内容:() ->内容var主体:一些视图{自我内容()}}

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

只需使用 LazyView 就可以解决您当前的问题只要它是层次结构中的顶部视图,就像您将它推入 NavigationView 或者如果你提供它.

然而,这是@user3441734 评论的来源.您真正需要做的是将 model 的所有权保留在您的 View 之外的某处,因为在#1.

i have a structure like that

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

///where View A contain an request trigger in view Appear

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 has the same code than ViewB, Receive id, store, and request api to collect data.

But viewB list not being refreshed. i also noticed viewB's

@ObservedObject var model: model = model()

was instantiate multiple time's

Debugging, i found every navigationLink instantie it's destination even before it being trigger. that's not a problem usually,

but in my case i feel like ViewB model is being instantiate 2 time, and my onApear call the wrong one, reason why self.objectWillChange.send() not refreshing my view

解决方案

There are two issues here:

  1. SwiftUI uses value types that that get initialized over and over again each pass through body.
  2. Related to #1, NavigationLink is not lazy.

#1

A new ListObj gets instantiated every time you call ViewA.init(...). ObservedObject does not work the same as @State where SwiftUI keeps careful track of it for you throughout the onscreen lifecycle. SwiftUI assumes that ultimate ownership of an @ObservedObject exists at some level above the View it's used in.

In other words, you should almost always avoid things like @ObservedObject var myObject = MyObservableObject().

(Note, even if you did @State var model = ListObj() it would be instantiated every time. But because it's @State SwiftUI will replace the new instance with the original before body gets called.)

#2

In addition to this, NavigationLink is not lazy. Each time you instantiate that NavigationLink you pass a newly instantiated ViewA, which instantiates your ListObj.

So for starters, one thing you can do is make a LazyView to delay instantiation until NavigationLink.destination.body actually gets called:

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

Now you can do NavigationLink(destination: LazyView { ViewA() }) and instantiation of ViewA will be deferred until the destination is actually shown.

Simply using LazyView will fix your current problem as long as it's the top view in the hierarchy, like it is when you push it in a NavigationView or if you present it.

However, this is where @user3441734's comment comes in. What you really need to do is keep ownership of model somewhere outside of your View because of what was explained in #1.

这篇关于ObervableObject 被多次初始化,并且没有刷新我的视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆