ObervableObject 被多次初始化,并且没有刷新我的视图 [英] ObervableObject being init multiple time, and not refreshing my view
问题描述
我有这样的结构
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() 不刷新我的视图的原因
这里有两个问题:
- SwiftUI 使用每次通过
body
时都会反复初始化的值类型. - 与#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:
- SwiftUI uses value types that that get initialized over and over again each pass through
body
. - 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屋!