I have been reading about the property wrappers in SwiftUI and I see that they do a great job, but one thing which I really don't get is the difference between @EnvironmentObject and @ObservedObject.
From what I learned so far, I see that @EnvironmentObject is used when we have an object that is needed in various places in our app but we don't need to pass it through all of them. For example if we have hierarchy A -> B -> C -> D and the object is created at A, it is saved in the environment so that we can pass it directly from A to D, if D needs it.
If we use @ObservedObject which is created at A and needs to be passed to D, then we need to go through B and C as well.
But I still don't know how to decide which one to use. Here are 2 example projects which I made:
struct ContentView2: View {
var order = Order2()
var body: some View {
VStack {
EditView2()
DisplayView2()
}
.environmentObject(order)
}
}
struct EditView2: View {
@EnvironmentObject var user: Order2
var body: some View {
HStack{
TextField("Fruit", text: $user.item)
}
}
}
struct DisplayView2: View {
@EnvironmentObject var user: Order2
var body: some View {
VStack{
Text(user.item)
}
}
}
class Order2: ObservableObject {
@Published var item = "Orange"
}
and
struct ContentView: View {
var order = Order()
var body: some View {
VStack {
EditView(order: order)
DisplayView(order: order)
}
}
}
struct EditView: View {
@ObservedObject var order: Order
var body: some View {
HStack{
TextField("Fruit", text: $order.item)
}
}
}
struct DisplayView: View {
@ObservedObject var order: Order
var body: some View {
VStack{
Text(order.item)
}
}
}
class Order: ObservableObject {
@Published var item = "Apple"
}
Both codes do the same update of the view. Also both ContentViews, pass an Order object. The difference is that Environment passes .environmentObject(order) and Observed passes it directly EditView(order: order). For me, both do same job, only their declaration is different, therefore I would appreciate some explanation or a better example.
ObservedObjects are usually used when the data is stored outside of the view so when the view is redrawn, we won't lose the data. StateObjects are usually used when that data needs to be stored inside of the view. Many people are confused by these two property wrappers.
When using observed objects there are three key things we need to work with: the ObservableObject protocol is used with some sort of class that can store data, the @ObservedObject property wrapper is used inside a view to store an observable object instance, and the @Published property wrapper is added to any ...
A property wrapper type that instantiates an observable object.
If you're finding it hard to remember the distinction, try this: whenever you see “State” in a property wrapper, e.g. @State , @StateObject , @GestureState , it means “the current view owns this data.” SPONSORED In-app subscriptions are a pain. The code can be hard to write, hard to test, and full of edge cases.
So if you want the ObservableObject you create to be reset or recreated every time the view is discarded and reappeared, you should mark the property with @ObservedObject. I’d like to think @EnvironmentObject property wrapper is a more global version of @State, or to be more precise, an @ObservedObject.
You should use @StateObject for any observable properties that you initialize in the view that uses it. But if the ObservableObject is created externally and passed to the view or even shared among views, you should use @ObservedObject. Since @StateObject couldn’t accomplish that.
The way @EnvironmentObject works is when called within a view, it looks from an object of that type in the environment (in other words, from any parent above it that has specified an environmentObject ), and then lets you use it.
If the observable object happens to have several views using its data, either option will automatically notify them all. Warning: When you use a custom publisher to announce that your object has changed, this must happen on the main thread. What is @StateObject? Somewhere between @State and @ObservedObject lies @StateObject.
As you've noticed an @ObservedObject
needs to be passed from view to view. It may be better for a simple view hierarchy when you don't have too many views.
Let's assume you have the following hierarchy:
ViewA -> ViewB -> ViewC -> ViewD
Now if you want your @ObservedObject
from the ViewA
to be in the ViewB
there's no problem with passing it directly in init
.
But what if you want it in the ViewD
as well? And what if you don't need it in the ViewB
and ViewC
?
With an @ObservedObject
you'd need to manually pass it from the ViewA
to the ViewB
and then to the ViewC
, and then to the ViewD
. And you'd need to declare it in every child view.
With an @EnvironmentObject
it's easy - just pass it to the top-level view:
ViewA().environmentObject(someObservableObject)
Then you only declare it in the view that uses it - this may make your code more readable.
Note
Every object in the environment (view hierarchy) can access the injected @EnvironmentObject
. If you don't want this (privacy is important) you may need to pass it as an @ObservedObject
instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With