Search code examples
swiftswiftuiobservableobjectobservedobject

SwiftUI: ObservedObject/ObservableObject weird behavior


I recently encountered the following problem in SwiftUI with ObservedObject/ObservableObject:

If the ObservedObject/ObservableObject is publishing in a View, the body property is recalculated - as expected.

But if there is a sub View in the body property which also has an ObservedObject, the View, strangely enough, not only recalculates the body property of the sub View, but the entire object.

The state of the ObservedObject is of course lost from the sub View.

Of course, you can prevent this by adding the ObservableObject to the sub View through .environmentObject(), but I don't think that's the best solution, especially with more complex view hierarchies.

Here an example Code:

struct ContentView: View {
    
    @ObservedObject var contentViewModel: ContentViewModel = ContentViewModel()
    
    var body: some View {
        VStack {
            Button {
                self.contentViewModel.counter += 1
            } label: {
                Text(String(self.contentViewModel.counter))
            }
            
            SubView()
        }
    }
}
class ContentViewModel: ObservableObject {
    @Published var counter: Int = 0
}

And the sub View:

struct SubView: View {
    
    @ObservedObject var subViewModel: SubViewModel = SubViewModel()
    
    
    var body: some View {
        Button {
            self.subViewModel.counter += 1
        } label: {
            Text(String(self.subViewModel.counter))
        }
    }
}
class SubViewModel: ObservableObject {
    @Published var counter: Int = 0
}

And here how the sample Code looks/works:

Preview

The last weird thing I realised, this is only the case if you use Observed Object. If I would replace in the sub View @ObservedObject var subViewModel: SubViewModel = SubViewModel() with @State var counter: Int = 0 it is working fine again and the state is preserved.

Maybe im missing out on something, but im really confused. Im very grateful for any answers and solutions. If you have further questions fell free to leave a comment, I will answer it within 24h (as long as the question is open).


Solution

  • I found a perfect solution:

    Either you replace the @ObservedObject with @StateObject (thx @Raja Kishan) in iOS 14 (SwiftUI v2.0) or you can create a Wrapper for the View and simulate the behaviour of @StateObject in iOS 13 (SwiftUI v1.0):

    struct SubViewWrapper: View {
        @State var subViewModel: SubViewModel = SubViewModel()
    
        var body: some View {
            SubView(subViewModel: self.subViewModel)
        }
    }
    

    and then use SubViewWrapper() in ContentView instead of SubView()

    struct ContentView: View {
        
        @ObservedObject var contentViewModel: ContentViewModel = ContentViewModel()
        
        var body: some View {
            VStack {
                Button {
                    self.contentViewModel.counter += 1
                } label: {
                    Text(String(self.contentViewModel.counter))
                }
                
                SubViewWrapper()
            }
        }
    }