Property Wrappers in SwiftUI
Let’s take a look at the property wrappers that are used extensively in SwiftUI to make changes to your view’s data values and objects.
Even though property wrappers are a feature of the Swift language introduced with Swift 5.1, these are also widely used in SwiftUI.
SwiftUI trusts heavily on the property wrappers to make out a code easier to read, write & maintain.
This article is all about how to use SwiftUI property wrappers to manage changes to its data values and objects with the @State
, @Binding
, @Published
, @Environment
, @ObservedObject
, @StateObject
and @EnvironmentObject
property wrappers.
@State
The @State
is used inside of View objects and allows your view to responding to any changes made to @State
. You use @State
for properties that are owned by the view that it’s contained in.
@State
should be used with simple struct types such as string, integer, and arrays and generally shouldn’t be shared with other views. If you want to share values across views, you should use @ObservedObject
or @EnvironmentObject
instead, both of those will ensure that all views will be refreshed when the data changes. (You can find more details about these property wrappers below 🤓.)
Note: External sources shouldn’t modify your @State
properties, so you should mark your @State
properties private
as follows:
<@State private var name = “ ”
Let’s glance at the following sample I prepared for the @State
property wrapper.
If we do not use the @State
property wrapper for the property whose value we want to change at run time, we will get the error that appears on lines 29 and 30 in the image below. Xcode compiler would not compile this, because structs are immutable. It is saying you can not assign a value to the property of View because it’s in a struct, hence it is immutable.
We need to solve this by using @State
as a property wrapper. By adding @State
lines 12 and 13, the Xcode now compiles the code and works fine 😊.
@Binding
The @Binding
property wrapper is used to synchronize the states between two views. In other words, the @Binding
property wrapper is used for properties passed by another view.
Let’s glance at the following sample I prepared to better understand the @Binding
property wrapper.
This is a basic sample of how to share data between views in SwiftUI. As you can see, this uses the showingName
for the view’s isPresented
parameter, so when this boolean is true, the NameView
will be displayed. However, it can close the NameView
itself by setting the showingName
of the NameView
back to false
. This is exactly what @Binding
property wrapper is for: it lets us create a property in the add user view that says “this value will be provided from elsewhere, and will be shared between us and that other place.”
As seen in the sample, we should use @Binding
a property wrapper when need to read and write access to a property that’s owned by a parent view.
@ObservedObject
How can we notify the UI that when data is updated or downloaded, the UI must be refreshed to display new data? In SwiftUI, we can easily do this using the @ObservedObject
property wrappers.
NOTE: The view does not create the instance of the
@ObservedObject
itself. (if it does, you need a@StateObject
)
Let’s glance at the following sample I prepared to better understand the @ObservedObject
property wrapper.
It has drawn your attention that I also use the @Published
property wrapper in this sample. After reading the @Published
property wrapper section, you will understand why I use it. 👇
@Published
@Published
is the most useful property wrapper in SwiftUI. When the property or value of an object marked @Published
is changed, all views using that object will be reloaded to reflect those changes.
Let’s glance again at the sample for the @ObservedObject
property wrapper to better understand the @Published
property wrapper.
class User : ObservableObject { internal init (name: String, age: Int){ self.name = name self.age = age } var name : String @Published var age : Int }
That User
class uses @Published
so it will automatically send change announcements when theage
variable changes, and DetailView
uses @ObservedObject
to watch for those announcements.
NOTE: Without
@ObservedObject
the change, announcements would be sent but ignored!!!
@StateObject
Introduced with iOS 14 and SwiftUI 2.0, @StateObject
is a property wrapper type that instantiates an Observable Object.
@StateObject
allows us to keep a reference to an object within a view
With the Observable Object protocol, we define in our class.
It uses @Published
properties to maintain values, which can be used to
write and read to cause views to be re-rendered.
It’s important to use @StateObject
whenever you need a reference type for the object to stay alive and not get released/destroyed when views are recreated. Because views are structs (value types).
NOTE: You should only use
@StateObject
once per object, which should be in the view responsible for creating the object. All other views that share your object must use@ObservedObject
.
Let’s glance at the following sample I prepared to better understand the @StateObject
property wrapper.
Where/When would you want to use @StateObject
;
- If you have an object with
@Published
properties
that needs to be alive as long as the view is. - You want to avoid using
@ObservedObject
and use this for the view that
need to maintain a reference to this object for as long as the view is
Alive - If you need to manage and maintain multiple properties and objects
within a class using an Observable Object with@StateObject
would be
cleaner.
@EnvironmentObject
You may want to use a piece of data in the home view and also deep in a settings menu, but if we don’t want every view in between to know about that data, in other words, we don’t need it, we need to use the @EnvironmentObject
property wrapper.
@EnvironmentObject
has a lot in common with @ObservedObject
, they both must reference a class compatible with Observable Object, both can be shared across many views, and both update all monitored views whenever significant changes occur.
NOTE: When a view using
@EnvironmentObject
is shown, SwiftUI will immediately search the environment for an object of the correct type. If no such object is found, i.e. you forgot to place it in the environment, your app will crash immediately. When you use@EnvironmentObject
you are effectively promising that the object will be present in the environment when it is needed, which is a bit like using an implicitly unwrapped optional.
Notice how the random
property isn’t given a default value, by using @EnvironmentObject
we’re saying that value will be provided by the SwiftUI environment rather than explicitly created by this view.
CONCLUSION
@State
should be used with simple struct types such as Int, Bool, or String and generally shouldn’t be shared with other views.@Binding
should be used to synchronize the states between two views. In other words, it should be used for properties passed by another view/@ObservedObject
should be used to notify the UI that the UI needs to be refreshed to display new data when data is updated or downloaded. Don’t forget to use@Published
property wrappers on the properties you would like to cause the view to re-render, or you’d like to update from a view!!!@StateObject
should be used to create any type that is more complex than what@State
can handle. As with@ObservedObject
, don’t forget to use@Published
property wrappers on properties you want to cause the View to be re-rendered or updated from a view!!! (Hint: Always use@StateObject
when you are instantiating a model, and ensure that the type conforms to Observable Object.😉)@EnvironmentObject
should be used to use up an Observable Object that has already been created in a parent view and then attached via the view’senvironmentObject()
view modifier.
REFERENCES
- https://www.hackingwithswift.com/quick-start/swiftui/all-swiftui-property-wrappers-explained-and-compared
- https://www.raywenderlich.com/21522453-swiftui-property-wrappers
That’s all for now. Thanks for reading. I hope it was useful.
Happy Coding !! 🤓