Introducing SwiftUI’s New @AppStorage, @StateObject, and @SceneStorage Property Wrappers
iOS 14 brings brand new property wrappers for persisting data in SwiftUI
SwiftUI got some great new enhancements during WWDC 2020. Among the things that stood out were additions to the SwiftUI property wrapper arsenal. Property wrappers are used heavily in SwiftUI applications for updating and observing views and are a crucial part of SwiftUI data management.
iOS 14 offers us three new property wrappers for data persistency:
@AppStorage
@StateObject
@SceneStorage
In the next few sections, we’ll look at each of them in the new Xcode 12 (in beta at the time of writing).
SwiftUI @AppStorage
An AppStorage
property wrapper is used to read and write values to the UserDefaults
. Every time the value of the AppStorage
property wrapper changes, the SwiftUI view is invalidated and redrawn.
It behaves the same way as @State
property wrappers, except that it’s used to communicate between a UserDefaults
key and the SwiftUI view in a convenient way. The following code shows how we used UserDefaults
with the State
property wrapper before iOS 14:
@State var name: String = "" { | |
get { | |
UserDefaults.standard.string(forKey: "name") | |
} | |
set { | |
UserDefaults.standard.set(newValue, forKey: "name") | |
} | |
} |
The new @AppStorage
property wrapper does this in a single line:
@AppStorage("name") var name: String = "hey"
AppStorage
also allows you to use a different suiteName
for UserDefaults
other than standard
.
Additionally, we can also set the default value for the UserDefaults
key directly inside @AppStorage
by using the wrappedValue
argument.
Here’s the syntax for each of the cases above:
Notice how in the second case, the @AppStorage
type is implicitly determined from the wrappedValue
set as boolean.
It might seem attractive to use AppStorage
in TextField
— especially since storing and updating login credentials is a common use case. But in doing so, your UserDefaults
would update every time the user enters or deletes something from the TextField, which may not be ideal.
A better scenario for using @AppStorage
is in counter-based applications to persist values across the app sessions. Or even in the new SwiftUI TextEditor
to build a handy Notepad application.
@AppStorage("text") var text: String = "" | |
var body: some View { | |
Text($text) | |
} |
SwiftUI @StateObject
StateObjects
have been introduced to fill the void between @EnvironmentObject
and @ObservedObject
.
While the EnvironmentObject
property wrapper is used as a shared data that’s available across all SwiftUI views in your application, ObservedObject
is typically used for plugging data source classes that conform to the ObservableObject
protocol.
@ObservedObject
set in one SwiftUI can be shared with other views as well, which can inadvertently lead to tricky issues. For instance, if you try to update a SwiftUI view (maybe by changing the state) that has an ObservedObject
initialized, the model would get recreated:
@ObservedObject var model = MyViewModel()
Though you can get rid of the MyViewModel()
default value above and pass the model as a parameter in init
, when you’re sharing the ObservedObject
with other child views, you’d lose the ability to determine the source of truth.
SwiftUI in iOS 14 brings a new @StateObject
property wrapper that doesn’t get reinitialized across state changes.
So now, you can happily replace @ObservedObject
with @StateObject
like this:
@StateObject var model = MyViewModel
SwiftUI @SceneStorage
The new SceneStorage
property is handy in state restorations in applications with multiple-window support — commonly built on iPadOS and macOS.
Unlike AppStorage
, it doesn’t save data in UserDefaults
. Instead, it’s just a state property that’s unique for each scene in your application.
@SceneStorage("isLoggedIn") var isLoggedIn = false
Conclusion
In this article, we saw the three new property wrappers introduced in SwiftUI with iOS 14. Each of them helps do state persistence in different ways. While StateObjects
are initialized once only for the view, the SceneStorage
property is unique to each scene. Lastly, AppStorage
is a state property wrapper for observing UserDefaults
and causing a view to redraw on updates.