Reputation: 61
I created a Model like this:
class TestModel: ObservableObject {
@Published var num: Int = 0
}
Model is be used in "Home" view and "Home"s child view "HomeSub"
struct Home: View {
@StateObject var model = TestModel()
var body: some View {
NavigationView(content: {
NavigationLink(destination: HomeSub(model: model)) { Text("\(model.num)") }
})
}
}
struct HomeSub: View {
//1
@StateObject var model = TestModel()
//2
@ObservedObject var model = TestModel()
var body: some View {
VStack {
Text("\(model.num)")
.padding()
.background(Color.red)
Button("Add") {
model.num += 1
}
}
.onChange(of: model.num, perform: { value in
print("homeSub: \(value)")
})
}
}
In HomeSub view, what is the difference between 1 and 2? When I run the project, they have exactly the same behavior.
Upvotes: 3
Views: 1583
Reputation: 85
In this case it is better to use @ObservedObject
as you are injecting the value from the parent. @StateObject
is designed to hold an object independently on the View (it's lifecycle is managed by SwiftUI). It is not needed here because of the injection.
Upvotes: 0
Reputation: 29329
They are used almost interchangeably and in your setup create 2 instances of the same Model.
@StateObject
's lifecycle is managed by SwiftUI, it is only available in iOS 14+.
https://developer.apple.com/documentation/swiftui/stateobject
@ObservableObject
's lifecycle is managed by the developer (Can sometimes be reinitialized unintentionally with a View
refresh because it is supposed to come from a Parent View
), it is available in iOS 13+.
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
Upvotes: 0
Reputation: 154613
As you've written it, both @StateObject
and @ObservedObject
are doing the same thing in the child view. But, neither is correct because they are unnecessarily creating a new TestModel
just to toss it and replace it with the one being passed in.
The correct way to write the child view is:
@ObservedObject var model: TestModel
In this case, no initial value is assigned to model
in the child view, which means the caller will have to provide it. This is exactly what you want. One source of truth which is the model
in the parent view.
Also, state variables (both @State
and @StateObject
) should be private
to a view and should always be marked with private
. If you had done this:
@StateObject private var model = TestModel()
in the child view, then you wouldn't have been able to pass the model from the parent view and you would have seen that only @ObservedObject
can be used in this case.
Upon further testing, it seems that Swift/SwiftUI avoids creating the TestModel
in the child view when it is written as @ObservedObject var model = TestModel()
, but that syntax is still misleading to the reader and it should still be written as @ObservedObject var model: TestModel
because that makes it clear that model
is being initialized from somewhere else (that is, from the parent view).
Upvotes: 6