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.
Property wrappers for values and objects
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
AddThingViewappears, 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.
Tapping return saves and exits.
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
@Stateand@Bindingto manage changes to user interface values. -
Access
@Environmentvalues as@Environmentview properties or by using theenvironmentview modifier. -
Use
@StateObjectand@ObservedObjectto manage changes to data model objects. The object type must conform toObservableObjectand should publish at least one value. -
For more flexible access to an
ObservableObject, instantiate it as a@StateObjectthen pass it in theenvironmentObjectview modifier. Declare an@EnvironmentObjectproperty in any subviews that need access to it. -
When prototyping your app, you can use
@Stateand@Bindingwith 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!

