SwiftUI Property Wrappers
Learn different ways to use SwiftUI property wrappers to manage changes to an app’s data values and objects. By Audrey Tam.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
SwiftUI Property Wrappers
35 mins
- Getting Started
- Tools for Managing Data
- Property Wrappers
- Managing UI State Values
- Managing ThingStore With @State and @Binding
- Using a TextField
- Accessing Environment Values
- Modifying Environment Values
- Managing Model Data Objects
- Class and Structure
- Managing ThingStore With @StateObject and @ObservedObject
- Refactoring TIL
- Using Thing Structure
- Navigating to ThingView
- Adding a New Thing From ThingView
- Using @EnvironmentObject
- Wrapping Up Property Wrappers
- Wrapping Values
- Wrapping Objects
- One More Thing
- Where to Go From Here?
Wrapping Up Property Wrappers
Here’s a summary to help you wrap your head around property wrappers.
First, decide whether you’re managing the state of a value or the state of an object. Values are mainly used to describe the state of your app’s user interface. If you can model your app’s data with value data types, you’re in luck because you have a lot more property wrapper options for working with values. But at some level, most apps need reference types to model their data, often to add or remove items from a collection.
Wrapping Values
@State
and @Binding
are the workhorses of value property wrappers. A view owns the value if it doesn’t receive it from any parent views. In this case, it’s a @State
property — the single source of truth. When a view is first created, it initializes its @State
properties. When a @State
value changes, the view redraws itself, resetting everything except its @State
properties.
The owning view can pass a @State
value to a subview as an ordinary read-only value or as a read-write @Binding
.
When you’re prototyping an app and trying out a subview, you might write it as a stand-alone view with only @State
properties. Later, when you fit it into your app, you just change @State
to @Binding
for values that come from a parent view.
Your app can access the built-in @Environment
values. An environment value persists within the subtree of the view you attach it to. Often, this is just a container like VStack
, where you use an environment value to set a default like font size.
You can store a few values in the @AppStorage
or @SceneStorage
dictionary. @AppStorage
values are in UserDefaults
, so they persist after the app closes. You use a @SceneStorage
value to restore the state of a scene when the app reopens. In an iOS context, scenes are easiest to see as multiple windows on an iPad.
Wrapping Objects
When your app needs to change and respond to changes in a reference type, you create a class that conforms to ObservableObject
and publishes the appropriate properties. In this case, you use @StateObject
and @ObservedObject
in much the same way as @State
and @Binding
for values. You instantiate your publisher class in a view as a @StateObject
then pass it to subviews as an @ObservedObject
. When the owning view redraws itself, it doesn’t reset its @StateObject
properties.
If your app’s views need more flexible access to the object, you can lift it into the environment of a view’s subtree, still as a @StateObject
. You must instantiate it here. Your app will crash if you forget to create it. Then you use the .environmentObject(_:)
modifier to attach it to a view. Any view in the view’s subtree can then subscribe to the publisher object by declaring an @EnvironmentObject
of that type.
To make an environment object available to every view in your app, attach it to the root view when the App
creates its WindowGroup
.
One More Thing
There are two small issues with the text fields. Fixing them would improve your users’ experience, mainly because they pretty much expect this behavior:
- When
AddThingView
appears, the focus should be in the first text field and the keyboard should be active. - Tapping return after typing the long form of the acronym should perform the same action as tapping Done.
For the first issue, there’s no native SwiftUI way to programmatically make a TextField
first responder. Solutions involve third party packages or a custom UIViewRepresentable
text field that conforms to UITextFieldDelegate
.
For the second issue, there’s a long-version TextField
initializer.
First, in AddThingView.swift, extract the Done button action into a method:
private func saveAndExit() {
if !short.isEmpty {
someThings.things.append(
Thing(short: short, long: long))
}
presentationMode.wrappedValue.dismiss()
}
// ...
Button("Done") { saveAndExit() }
You’ll use this method a second time in this file in the TextField
action.
Replace TextField("Thing I Learned", text: $long)
with the following:
TextField(
"Thing I Learned",
text: $long,
onEditingChanged: { _ in },
onCommit: { saveAndExit() }
)
Tapping return triggers the onCommit
action.
Live-preview ContentView
and run it through its paces.
Where to Go From Here?
You can download the final project by using the Download Materials button at the top or bottom of this page.
You’ve learned a lot about managing mutable data values and objects in a SwiftUI app with the @State
, @Binding
, @Environment
, @StateObject
, @ObservedObject
and @EnvironmentObject
property wrappers. This includes:
-
Use
@State
and@Binding
to manage changes to user interface values. -
Access
@Environment
values as@Environment
view properties or by using theenvironment
view modifier. -
Use
@StateObject
and@ObservedObject
to manage changes to data model objects. The object type must conform toObservableObject
and should publish at least one value. -
For more flexible access to an
ObservableObject
, instantiate it as a@StateObject
then pass it in theenvironmentObject
view modifier. Declare an@EnvironmentObject
property in any subviews that need access to it. -
When prototyping your app, you can use
@State
and@Binding
with structures that model your app’s data. When you’ve worked out how data needs to flow through your app, you can refactor your app to accommodate data types that need to conform toObservableObject
.
If you have any questions or comments, join the forum below!