Swiftui: @Observedobject Redraws Every View

SwiftUI is it possible to update ObservedObject in the current view without redrawing current view?

SwiftUI is designed so that the code in your views is run multiple times so that it knows what to display, and it reruns any time a piece of state – whether it's @State, @StateObject, @ObservedObject, etc. – changes. That's why you're seeing multiple print statements – it's expected behaviour.

Here, your View2 and View3 are referencing the same data source, so of course if that data source changes in one, it's going to change in the other.

So when you have a one-element array and remove that element in one view, your other view is going to be re-evaluated because it's looking at exactly the same array. If you don't want that to happen, then you should not share state between views.

The key with successfully using SwiftUI is to ensure that your views' body code can be run, and rerun, as many times as the system wants to without introducing side-effects (the throwing of an error when calling let text = dataSource.listOfThings[0] with an empty array is one such side effect). If you try and fight against that, you'll have a devil of a time creating beautiful and performant user interfaces with SwiftUI.

SwiftUI: ObservableObject does not persist its State over being redrawn

Finally, there is a Solution provided by Apple: @StateObject.

By replacing @ObservedObject with @StateObject everything mentioned in my initial post is working.

Unfortunately, this is only available in ios 14+.

This is my Code from Xcode 12 Beta (Published June 23, 2020)

struct ContentView: View {

@State var title = 0

var body: some View {
NavigationView {
VStack {
Button("Test") {
self.title = Int.random(in: 0...1000)
}

TestView1()

TestView2()
}
.navigationTitle("\(self.title)")
}
}
}

struct TestView1: View {

@ObservedObject var model = ViewModel()

var body: some View {
VStack {
Button("Test1: \(self.model.title)") {
self.model.title += 1
}
}
}
}

class ViewModel: ObservableObject {

@Published var title = 0
}

struct TestView2: View {

@StateObject var model = ViewModel()

var body: some View {
VStack {
Button("StateObject: \(self.model.title)") {
self.model.title += 1
}
}
}
}

As you can see, the StateObject Keeps it value upon the redraw of the Parent View, while the ObservedObject is being reset.

run when a view redraws in SwiftUI

If I correctly understood your needs then you should do this not in view but in view model, like

class ViewModel: ObservableObject {
var onModelChanged: (_ old: Model, _ new: Model) -> Void
@Published var model = Model() {
didSet {
onModelChanged(oldValue, model)
}
}

init(onModelChanged: @escaping (_ old: Model, _ new: Model) -> Void = {_, _ in}) {
self.onModelChanged = onModelChanged
}
}

so instantiating ViewModel you can provide a callback to observe values changed in model and have old and new values, like

@StateObject var viewModel = ViewModel() {
print("Old value: \($0.someValue)")
print("New value: \($1.someValue)")
}

ObservedObject resets when I update the view using a property. Hence it leads to loss of data

SwiftUI views are structs, and therefore, immutable. When you update the state and cause the view to redraw, it actually creates a new instance of the view.

In your SongListView you have

@ObservedObject var model: SongListViewModel = SongListViewModel()

This means that each time your SongListView is re-drawn (which includes any time that player.isPlaying is changed), you are initialising model with a new instance of SongListViewModel.

You should remove the default value and supply the model via a parameter to the initialiser of SongListView -

@ObservedObject var model: SongListViewModel


Related Topics



Leave a reply



Submit