SwiftSwiftUIiOSApple
Last updated at 2023-07-27

How to use @StateObject, @EnvironmentObject, and ObservableObject in Swift

ClickUp
Note
AI Status
Last Edit By
Last edited time
Jul 27, 2023 10:29 AM
Metatag
Slug
stateobject-environmentobejct-in-swift
Writer
Published
Published
Date
Jul 27, 2023
Category
Swift
SwiftUI
iOS
Apple
You have a view that is so deep. This is because you want to split the children's view for easier work with them. A nested view requires you to share model data to its children so they are automatically updated when the data changes.
At first, you may solve the sharing data problem by using the view initializer. Doing so with a lot of views starts to be cumbersome for you. Passing from A to B to C can grow exponentially and can hurt your eyes just by looking at the source code. It also makes maintenance so painful. Suppose the view that uses the data is located 5 views deep, then you must pass the model several times before using it.
Using @EnvironmentObject can solve the above issue.
@EnvironmentObject helps you automagically get a reference to the model data and listen for change. Whenever data is updated, your view can react to the new data automatically.

How to define model data

Defining model data is easy. You just need to create a class that conforms to ObservableObject.
class UserViewModel: ObservableObject { @Published var name: String = "" @Published var age: Int = 0 }
🚨
Your model data must conform to ObservableObject in order to use @EnvironmentObject
The above UserViewModel defines two things:
  1. name is a published property of type String
  1. age is a published property of type Int
Updating those values means the view that reads them must also update.

How to send data to children

Sending data to children can be done using .environmentObject modifier. This modifier allows you to pass an object that conforms to the ObservableObject protocol.
Here is the code to use UserViewModel in ContentView
struct UserView: View { @EnvironmentObject var user: UserViewModel var body:some View { Text("User: \(user.name)") AgeView() } } struct AgeView: View { @EnvironmentObject var user: UserViewModel var body: some View { Text("Age: \(user.age)") } } struct ContentView: View { @StateObject private var user = UserViewModel() var body:some View { NavigationStack { VStack { UserView() Button("Update Name") { user.name = "Mozzlog" } Button("Update Age") { user.age += 1 } } } .environmentObject(settings) } }
🚨
You must provide ancestor view .environmentObject before read it using @EnvironmentObject. This also applies to the SwiftUI Preview. If you don’t do that, it can cause a crash.
The explanation of the above code is as follows:
  1. You have UserView which is responsible for displaying text showing the user name. It reads the user value from environment hence you write @EnvironmentObject var user: UserViewModel.
  1. You also have AgeView which is responsible for displaying text showing the user's age. It reads the user's age from the environment.
  1. UserView and AgeView don’t create a UserViewModel. They only read the value from environment, which supplied by ContentView
  1. ContentView doesn’t need to supply the UserViewModel object directly to UserView because calling .environmentObject(user) from the NavigationStack view automatically provides that object for its children.
  1. Now UserView and AgeView will read the user value from ContentView.user. Any changes from ContentView, like tapping the Update Name and Update Age buttons, will update these views.

Conclusion

To avoid passing data model multiple times, you can use @EnvironmentObject that will read the referenced value from its ancestor. You send the model data using the .environmentObject modifier from the top-most ancestor. The model data will be accessible recursively to all children.
 

Discussion (0)

Related Posts