Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - ObservableObject created multiple times

I have created an ObservableObject in a View.

@ObservedObject var selectionModel = FilterSelectionModel()

I put a breakpoint inside the FilterSelectionModel's init function and it is called multiple times. Because this View is part of a NavigationLink, I understand that it gets created then and along with it, the selectionModel. When I navigate to the View, the selectionModel is created again.

In this same View I have a "sub View" where I pass the selectionModel as an EnvironmentObject so the sub-view can change it.

AddFilterScreen().environmentObject(self.selectionModel)

When the sub view is dismissed, the selectionModel is once more created and the changes made to it have disappeared.

Interesting Note: At the very top level is a NavigationView. IF I add

.navigationViewStyle(StackNavigationViewStyle())

to this NavigationView, my selectionModel's changes disappear. BUT if I do not add the navigationStyle, the selectionModel's changes made in the sub view remain!! (But I don't want a split nav view, I want a stacked nav view)

In both cases - with or without the navigationStyle, the selectionModel is created multiple times. I can't wrap my head around how any of this is supposed to work reliably.

like image 303
P. Ent Avatar asked Dec 30 '19 15:12

P. Ent


Video Answer


2 Answers

Latest SwiftUI updates have brought solution to this problem. (iOS 14 onwards)

@StateObject is what we should use instead of @ObservedObject, but only where that object is created and not everywhere in the sub-views where we are passing the same object.

For eg-

class User: ObservableObject {
    var name = "mohit"
}


struct ContentView: View {
  @StateObject var user = User()

  var body: some View {
    VStack {
      Text("name: \(user.name)")
      NameCount(user: self.user)
   }
  }
}


struct NameCount: View {
  @ObservedObject var user

  var body: some View {
    Text("count: \(user.name.count)")
  }
}

In the above example, only the view responsible (ContentView) for creating that object is annotating the User object with @StateObject and all other views (NameCount) that share the object is using @ObservedObject.

By this approach whenever your parent view(ContentView) is re-created, the User object will not be re-created and it will persist its @State, while your child views just observing to the same User object doesn't have to care about its re-creation.

like image 175
mohit kejriwal Avatar answered Sep 22 '22 18:09

mohit kejriwal


You can instantiate the observable object in the init method, in this way you will be able to hold its value or the value won't disappear.

Instantiate this way in the view file.

@ObservedObject var selectionModel : FilterSelectionModel

init() {
   selectionModel = FilterSelectionModel(value : "value to be saved from disappearing")
}

Instantiate this way in the viewModel file.

class FilterSelectionModel : ObservableObject {

  @Published var value : String

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

}

This is a workaround that I found, but still, the init method is called multiple times and I didn't get any success with this issue.

In order to stop multiple initializing of the ViewModels as the view is declared in the Navigation View and SwiftUI uses struct which is a value type, so eventually these are initialized before the view is presented, therefore you can convert that view into a LazyView, so that it will only be initialized once the view is about to be presented or shown.

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

You can call it like this...

 NavigationLink(destination: LazyView { ViewTobePresented() }) 
like image 32
Anshuman Singh Avatar answered Sep 19 '22 18:09

Anshuman Singh