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.