SwiftUI: What is @AppStorage property wrapper
Re-implementation for iOS 13 and without SwiftUI
In additon to pawello2222 answer, here. is the reimplementation of the AppStorage that I named it as UserDefaultStorage
:
@propertyWrapper
struct UserDefaultStorage<T: Codable> {
private let key: String
private let defaultValue: T
private let userDefaults: UserDefaults
init(key: String, default: T, store: UserDefaults = .standard) {
self.key = key
self.defaultValue = `default`
self.userDefaults = store
}
var wrappedValue: T {
get {
guard let data = userDefaults.data(forKey: key) else {
return defaultValue
}
let value = try? JSONDecoder().decode(T.self, from: data)
return value ?? defaultValue
}
set {
let data = try? JSONEncoder().encode(newValue)
userDefaults.set(data, forKey: key)
}
}
}
This wrapper can store/restore any kind of codable into/from the user defaults. Also, it works in iOS 13 and it doesn't need to import SwiftUI
.
Usage
@UserDefaultStorage(key: "myCustomKey", default: 0)
var myValue: Int
Note that it can't be used directly as a State
This is a persistent storage provided by SwiftUI. This code will persist the email across app launches.
struct AppStorageView: View {
@AppStorage("emailAddress") var emailAddress = "[email protected]"
var body: some View {
TextField("Email Address", text: $emailAddress)
}
}
With pure SwiftUI code, we can now persist such data without using UserDefaults
at all.
But if you do want to access the underlying data, it is no secret that the wrapper is using UserDefaults
. For example, you can still update using UserDefaults.standard.set(...)
, and the benefit is that AppStorage observes the store, and the SwiftUI view will update automatically.
Disclaimer: iOS 14 Beta 2
In addition to the other useful answers, the types you can use in @AppStorage are (currently) limited to: Bool, Int, Double, String, URL, Data
Attempting to use other types (such as Array) results in the error: "No exact matches in call to initializer"
AppStorage
@AppStorage
is a convenient way to save and read variables from UserDefaults and use them in the same way as @State
properties. It can be seen as a @State
property which is automatically saved to (and read from) UserDefaults
.
You can think of the following:
@AppStorage("emailAddress") var emailAddress: String = "[email protected]"
as an equivalent of this (which is not allowed in SwiftUI and will not compile):
@State var emailAddress: String = "[email protected]" {
get {
UserDefaults.standard.string(forKey: "emailAddress")
}
set {
UserDefaults.standard.set(newValue, forKey: "emailAddress")
}
}
Note that @AppStorage
behaves like a @State
: a change to its value will invalidate and redraw a View.
By default @AppStorage
will use UserDefaults.standard
. However, you can specify your own UserDefaults
store:
@AppStorage("emailAddress", store: UserDefaults(...)) ...
Unsupported types (e.g., Array
):
As mentioned in iOSDevil's answer, AppStorage
is currently of limited use:
types you can use in @AppStorage are (currently) limited to: Bool, Int, Double, String, URL, Data
If you want to use any other type (like Array
), you can add conformance to RawRepresentable
:
extension Array: RawRepresentable where Element: Codable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode([Element].self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
}
Demo:
struct ContentView: View {
@AppStorage("itemsInt") var itemsInt = [1, 2, 3]
@AppStorage("itemsBool") var itemsBool = [true, false, true]
var body: some View {
VStack {
Text("itemsInt: \(String(describing: itemsInt))")
Text("itemsBool: \(String(describing: itemsBool))")
Button("Add item") {
itemsInt.append(Int.random(in: 1...10))
itemsBool.append(Int.random(in: 1...10).isMultiple(of: 2))
}
}
}
}
Useful links:
- What is the @AppStorage property wrapper?
- AppStorage Property Wrapper SwiftUI