SwiftUI Property Wrappers Simplified

João Gabriel
Cocoa Academy
Published in
8 min readApr 8, 2022

--

We can use some Property Wrappers to speed up the development of SwiftUI apps. In this tutorial we are going to understand in a simplified way, the meaning and the use cases of @State, @StateObject, @EnvironmentObject, @ObservedObject, @Binding, and @Published (this last one from Combine).

Data flow in SwiftUI

The data flow in a SwiftUI App influences the behavior of the interface, because it always reacts to it’ state.

Fluxo de estados dentro de um SwiftUI App.

As we can see, the View reacts to the state it has. The state is always changed by an action. An action can be made by an user, when it interacts with the App or an external event (could be some asynchronous task).

Let'’ understand how we can use property wrappers to deal with those states.

@State & @Binding

We can understand in a practical way that both @State & @Binding, are dedicated to store and refer to values. The difference is in the way that they refer to the data:

  • @State = Property that is the owner of the value itself. It has the value associated directly.
  • @Binding = Refer (and watch) an instance. This variable will always return the value of the original variable and propagate the changes made.

Both variables declare as @State & @Binding, can influence the behavior of the View they are.

Let’s verify some examples:

Practical example: Communication between 2 Views in the same View

Let’s see how it works in practice. Using 2 Views we can create a click counter system. Those 2 Views are the following:

  • View Status: Shows the current total number and it has another View inside (View Action).
  • View Action: Shows a button to increase the integer number. It has a blue background (just as a didactic purpose).

We can see this example working below:

Counter example.

As we can see, this main View has a text with the total number and another View (with the blue background) containing a button that increases the counter number. Those 2 Views can be seen separately bolow:

The 2 Views separated.

The code for the first View (CounterStatusView) that has the status is the following:

Code of CounterStatusView

The CounterStatusView, has an integer variable with theProperty Wrapper @State and it’ declared on the line 7. This variable receives an value (in this case is 0). This View has a vertical structure (VStask) with the following elements:

  • Text containing “Counter”
  • Text information about the total “Total is counter” (in this case counter is a reference to the variable with @State)
  • A child View ConterActionView that receives a parameter as @Binding referring to the @State variable. This binding is accessed by using the “$” before the variable’ name.

The code of the child’ View (CounterActionView) is the following:

Code of CounterActionView

The struct CounterActionView, has a integer variable called counter that is declared on line 7, with the Property Wrapper @Binding. I means that this variable is going to be just a reference, so it will work like a variable’s observer that will be received in the init of the View. The variable reference is needed on the init of the View (as we can see on line 27 of preview, where in this case we have a constant).

Practical Example: Communication between 2 Views in different screens

Let’ create an activation system with 2 screens to see how it works:

  • Status screen: Shows if the state is active or not and a button that shows the manager screen.
  • Manager screen: Shows a button to change the status and another button to go back to the last screen.

We can see the example running below:

Activation system example.

The first screen’s code (Status View) is the following:

Code of ActivationStatusView

The visual result of ActivationStatusView is the following:

Preview of ActivationStatusView

The result of ActivationStatusView is a View that will update depending on the status variable of isActivated that has the @State property wrapper (declared on line 7), it means that:

  • When isActivated is true: The text shows “Status: ON” and a green circle.
  • When isActivated is false: The text shows “Status: OFF” and a red circle.

When clicking on the Change button, it changes the isShowingSheet that shows the ActivationManagerView. The code of ActivationManagerView is the following:

Code of ActivationManagerView

The visual result of ActivationManagerView is the following:

Preview of ActivationManagerView

The result of ActivationManagerView is a view that updates depending on the status of the bool variable isActivated and has the @Binding property wrapper (declared on line 7) that is watching the variable with @State of the preview screen (ActivationStatusView). So it works like this:

  • When isActivated is true: The button shows “Update to OFF” text.
  • When isActivated is false: The button shows “Update to ON” text.

Clicking the button, updates the isActivated value, that is a reference to the @State variable declared on the preview View (ActivationStatusView).

@StateObject & @ObservedObject

Those 2 Property Wrappers works in a similar way. The difference is that it works with you custom objects. It’ possible to use our custom classes as a state for our Apps, the only thing we need to do is to conform the class with the ObservableObject protocol. When doing it, it's possible to use the @Published in our class variables to trigger it’s View’ update. So, let’s understand how those 2 property wrappers work:

  • @StateObject = Associated with the variable that is the owner of the instance of the out custom class conformed with ObservableObject. It has the instance directly.
  • @ObservedObject = Refer (and watch) an instance.

Let’s see some examples below:

Practical example: Communication between 2 Views of the same screen

Let’s return to our example of the counter system. The interface will stay the same. In this example we are going to see the @StateObject and @ObservedObject property wrappers to refer to our custom class conformed with ObservableObject protocol. We can see the custom class code below:

Something class conforming to the ObservableObject protocol and containing a variable with @Published property wrapper.

The Something class conforms to the ObservableObject protocol and has a @Published variable. A good explanation of @Published is the Apple Decumentation:

“When the property changes, publishing occurs in the property’s willSet block, meaning subscribers receive the new value before it’s actually set on the property."

Using our custom class, we can change our project to use the values from the Somehting class. The View that shows the status will have the following code:

Code of the CounterStatusView using a StateObject.

As we can see in line 3, the variable something has the @StateObject property wrapper and it has the instance of the custom class, meaning that it is the owner of the value. The View access the value from the something object on line 10. Then the View uses the something instance as a parameter for initializing the CounterActionView, that we can see the code below:

Code of CounterActionView that receives a ObservedObject that references the instance of our custom class

This class has a @ObservedObject variable on line 3. This variable is going to refer to the instance of Something declared previously. This View uses the properties inside of the object on line 10, and changes it on line 7.

It is also possible to use the implementation of line 8 (commented code) that executes a method of Something, it makes the update be delayed by 1 second.

Practical example: Communication between 2 Views in different screens

Let’s go back to our validation system example. The interface is going to be the same, but in this case we are going to be using @StateObject and @ObservedObject to refer to the ActiviationModel model that conforms with ObservableObject.

ActivationModel model conforming to the ObservableObject protocol

The isActivated variable is inside the class that conforms with ObservableObject and it has a @Published that will notify all the references to the object instance when it changes.

The new Status View with the new model reference, has the following code:

ActivationStatusView with the reference to the new model and conforming to the ObservableObject protocol.

The @StateObject property wrapper is used on line 3 to handle the object tha conforms with ObservableObject. This variable receives the object, so it is owner of the object.

The Manager View can change the status of the activation system using the reference of the ObservableObject:

ActivationManagerView with a variable referring to the model instantiated on the preview screen

In this case the model variable on line 3 is only referring to the object of the preview screen. This variable uses the @ObservedObject property wrapper.

@EnvironmentObject

This property wrapper is also used to refer to the class that conforms with ObservableObject protocol… But what is the difference?

The difference is that when we associate an environment object to a View, this object becomes available for all the views hierarchy, meaning that it can be referred by any child View at any level inside the View’ hierarchy. When a View needs to refer to the environment object, it only needs to have a variable with @EnvironmentObject property wrapper declared with the same type as the State Object. We can have an idea of how it works by looking the scheme below:

Didactic example of @EnvironmentObject use case.

Let’s understand what happens in this stack of views:

  • Gray View has. a reference to the instance of an object that conforms to ObservableObject. This Gray View insert this object inside it’s child view (Blue View) using the environmentObject().
  • Blue View (child of Gray View)do nothing about the environment object.
  • Yellow View (child of Blue View) refers to the environment object using a variable with the @EnviromentObject property wrapper.

The code for the 3 Views are the following:

Code of 3 Views, using a @EnvironmentObject property wrapper to refer to the object instance when needed.

As we can see, in this case the Blue View (IntermidiateView) do not refer the Environment Object. The Yellow View refers to it because it is inside the View’ hierarchy and it can have a variable with a @EnvironmentObject property wrapper to refer to the model (line 30). When using a environment object, it’s not needed to pass the object view to view, because it’s already inside of the view’ hierarchy.

Good practices

Be careful when analyzing the use cases for using property wrappers, to use it in the most convenient way possible. use @StateObject and @ObservedObject if it’s only a few levels (about 2 levels) inside the hierarchy. If it’s needed to refer to the object inside more than 2 levels it could be the moment to analyze the possibility of using an @EnvironmentObject, because it will be easier and safer to handle the object inside the App.

Conclusion

We can take advantage of those communication mechanisms between Views at the same screen or different screens. It’s a good to alway analyze the good practices to use those property wrappers in the most convenient way possible, depending on the use case.

References

--

--