WWDC 2020 introduced a few more property wrappers. I don’t take system wrappers lightly, as they must be introduced for very good reason. So better know them well.
They are available in iOS 14, macOS 11 (aka 10.16), tvOS 14, and watchOS 7. Let’s stick with just the year.
Some of the new 2020 APIs: AppStorage, SceneStorage, ScaledMetric, StateObject, UIApplicationDelegateAdaptor.
@AppStorage for UserDefaults
AppStorage is the persistent storage provided by SwiftUI, with UserDefaults
as the underlying backing data. The following code will persist the email across launched.
struct AppStorageView: View {
@AppStorage("email") var email = "[email protected]"
var body: some View {
TextField("Email Address", text: $email)
}
}
Any change to the underlying UserDefaults
will publish the changes, therefore updating the SwiftUI view.
@SceneStorage for scenes
It is another persistent storage for SwiftUI, but for each scene state restoration. You’re not familiar with scene lifecycle, it is introduced in 2019 for multi-windows.
Unlike AppStorage,
The underlying data that backs SceneStorage is not available to you
NOTE: This can only be used with SwiftUI App + Scene structure. If you’re using the old AppDelegate way, there will be fatal error “@SceneStorage is only for use with SwiftUI App Lifecycle”.
@StateObject
Initiating with @ObservedObject
in a view is unsafe.
struct MyView: View {
@ObservedObject var myModel = MyModel() // UNSAFE
}
SwiftUI might create or recreate a view at any time, so it’s important that initializing a view with a given set of inputs always results in the same view. As a result, it’s unsafe to create an observed object inside a view.
Instead, use @StateObject
for a view (or structure), when it can be the owner.
For external dependencies, stick to @ObservedObject
and also @EnvironmentObject
.
struct MyView: View {
@StateObject var myModel = MyModel()
@ObservedObject var externalModel: ExternalModel
@EnvironmentObject var globalModel: GlobalModel
}
You can also create StateObject
in App
or Scene
– if they can be the owner and should hold on to the truth.
struct MyApp: App { // Or for Scene
@StateObject var store = Store()
}
@ScaledMetric
This scales a float according to the user’s Dynamic Type settings. All along, only fonts are scaled automatically. Now we can scale an image like this:
@ScaledMetric var length: CGFloat = 100
var body: some View {
Image(systemName: "bolt.fill")
.resizable()
.frame(width: length, height: length)
}
@UIApplicationDelegateAdaptor
If you are mixing UIKit’s AppDelegate and SwiftUI, then this is a way to access the app delegate. I don’t see any reason why this is even needed, but here’s the gist:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func foo() { print("foo") }
}
struct MyView: View {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some View {
Button("Foo") {
appDelegate.foo()
}
}
}