ObervableObject being init multiple time, and not refreshing my view

安稳与你 提交于 2020-03-04 23:08:13

问题


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


回答1:


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.



来源:https://stackoverflow.com/questions/60278829/obervableobject-being-init-multiple-time-and-not-refreshing-my-view

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!