Reputation: 312
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:
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).
Upvotes: 2
Views: 624
Reputation: 312
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()
}
}
}
Upvotes: 0
Reputation: 18904
Declare your ViewModel with @StateObject
. @StateObject
is not recreated for every view re-render more
struct SubView: View {
@StateObject var subViewModel: SubViewModel = SubViewModel() //<--- Here
var body: some View {
Button {
self.subViewModel.counter += 1
} label: {
Text(String(self.subViewModel.counter))
}
}
}
Upvotes: 3