SwiftUI:什么是@AppStorage 属性 包装器
SwiftUI: What is @AppStorage property wrapper
我曾经使用以下语句将重要的应用程序数据(如登录凭据)保存到 UserDefaults 中:
UserDefaults.standard.set("sample@email.com", forKey: "emailAddress")
现在,我了解到 SwiftUI 引入了新的 属性 包装器,名为:
@AppStorage
谁能解释一下新功能的工作原理?
AppStorage
@AppStorage
是一种从 UserDefaults 保存和读取变量并以与 @State
属性相同的方式使用它们的简便方法。它可以看作是一个 @State
属性 自动保存到(并从中读取)UserDefaults
.
你可以这样想:
@AppStorage("emailAddress") var emailAddress: String = "sample@email.com"
相当于此(在 SwiftUI 中 not 允许并且将 not 编译:
@State var emailAddress: String = "sample@email.com" {
get {
UserDefaults.standard.string(forKey: "emailAddress")
}
set {
UserDefaults.standard.set(newValue, forKey: "emailAddress")
}
}
请注意 @AppStorage
的行为类似于 @State
:对其值的更改将使视图无效并重新绘制视图。
默认情况下 @AppStorage
将使用 UserDefaults.standard
。但是,您可以指定自己的 UserDefaults
商店:
@AppStorage("emailAddress", store: UserDefaults(...)) ...
不支持的类型(例如,Array
):
如iOSDevil的所述,AppStorage
目前用途有限:
types you can use in @AppStorage are (currently) limited to: Bool, Int, Double, String, URL, Data
如果你想使用任何其他类型(如Array
),你可以添加符合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
}
}
演示:
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))
}
}
}
}
有用的链接:
这是SwiftUI提供的持久化存储。此代码将在应用程序启动时保留电子邮件。
struct AppStorageView: View {
@AppStorage("emailAddress") var emailAddress = "initial@hey.com"
var body: some View {
TextField("Email Address", text: $emailAddress)
}
}
使用纯 SwiftUI 代码,我们现在可以在根本不使用 UserDefaults
的情况下保留此类数据。
但是如果您确实想要访问底层数据,包装器正在使用 UserDefaults
已经不是什么秘密了。例如,您仍然可以使用 UserDefaults.standard.set(...)
进行更新,好处是 AppStorage 会观察商店,SwiftUI 视图会自动更新。
免责声明:iOS 14 测试版 2
除了其他有用的答案,您可以在@AppStorage 中使用的类型(当前)限于:Bool、Int、Double、String、URL、Data
尝试使用其他类型(例如 Array)会导致错误:“在调用初始化程序时没有完全匹配”
Re-implementation iOS 13 且没有 SwiftUI
除了 pawello2222 的回答,这里。是我将其命名为 UserDefaultStorage
:
的 AppStorage 的重新实现
@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)
}
}
}
此包装器可以 store/restore 任何类型的可编码 into/from 用户默认值。此外,它适用于 iOS 13,并且 不需要 导入 SwiftUI
.
用法
@UserDefaultStorage(key: "myCustomKey", default: 0)
var myValue: Int
注意不能直接作为State
使用
我们可以通过简单的方法进行测试:
struct Content: View {
private enum Keys {
static let numberOne = "myKey"
}
@AppStorage(Keys.numberOne) var keyValue2: String = "no value"
var body: some View {
VStack {
Button {
keyValue2 = "Hello"
print(
UserDefaults.standard.value(forKey: Keys.numberOne) as! String
)
} label: {
Text("Update")
}
Text(keyValue2)
}
.padding()
.frame(width: 100)
}
}
我试验了一种行为,想与您分享。我有一个视图(称为 MainView),它使用 @AppStorage Bool 来向用户显示或不显示消息。在此视图中,我还有许多视图显示从 JSON(城市)加载的数据。
我开发了一项 'Add to favorite' 功能,该功能使用 UserDefault 来存储用户在 his/her 收藏夹中添加的城市 ID 列表。奇怪的行为是:一旦用户将一个城市添加或删除为收藏夹,所有 MainView 的主体(及其所有 child 视图)都会更新。这意味着所有内容都被无故重新加载。
经过几个小时的调查,特别是重构了我所有的 ObservableObjects(模型)、我的@Published 变量等等,我发现是什么导致了这次大规模更新:我的 MainView 中的@AppStorage!一旦更新 'UserDefaults.standard' 中的任何键(在我的例子中是通过存储我的新收藏夹),所有使用 @AppStorage 的视图的主体都会更新。
所以@AppStorage 很棒而且易于使用。但要注意这种行为。当您设置一个甚至未在该视图中使用的键时,您不希望视图被更新!
我曾经使用以下语句将重要的应用程序数据(如登录凭据)保存到 UserDefaults 中:
UserDefaults.standard.set("sample@email.com", forKey: "emailAddress")
现在,我了解到 SwiftUI 引入了新的 属性 包装器,名为:
@AppStorage
谁能解释一下新功能的工作原理?
AppStorage
@AppStorage
是一种从 UserDefaults 保存和读取变量并以与 @State
属性相同的方式使用它们的简便方法。它可以看作是一个 @State
属性 自动保存到(并从中读取)UserDefaults
.
你可以这样想:
@AppStorage("emailAddress") var emailAddress: String = "sample@email.com"
相当于此(在 SwiftUI 中 not 允许并且将 not 编译:
@State var emailAddress: String = "sample@email.com" {
get {
UserDefaults.standard.string(forKey: "emailAddress")
}
set {
UserDefaults.standard.set(newValue, forKey: "emailAddress")
}
}
请注意 @AppStorage
的行为类似于 @State
:对其值的更改将使视图无效并重新绘制视图。
默认情况下 @AppStorage
将使用 UserDefaults.standard
。但是,您可以指定自己的 UserDefaults
商店:
@AppStorage("emailAddress", store: UserDefaults(...)) ...
不支持的类型(例如,Array
):
如iOSDevil的AppStorage
目前用途有限:
types you can use in @AppStorage are (currently) limited to: Bool, Int, Double, String, URL, Data
如果你想使用任何其他类型(如Array
),你可以添加符合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
}
}
演示:
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))
}
}
}
}
有用的链接:
这是SwiftUI提供的持久化存储。此代码将在应用程序启动时保留电子邮件。
struct AppStorageView: View {
@AppStorage("emailAddress") var emailAddress = "initial@hey.com"
var body: some View {
TextField("Email Address", text: $emailAddress)
}
}
使用纯 SwiftUI 代码,我们现在可以在根本不使用 UserDefaults
的情况下保留此类数据。
但是如果您确实想要访问底层数据,包装器正在使用 UserDefaults
已经不是什么秘密了。例如,您仍然可以使用 UserDefaults.standard.set(...)
进行更新,好处是 AppStorage 会观察商店,SwiftUI 视图会自动更新。
免责声明:iOS 14 测试版 2
除了其他有用的答案,您可以在@AppStorage 中使用的类型(当前)限于:Bool、Int、Double、String、URL、Data
尝试使用其他类型(例如 Array)会导致错误:“在调用初始化程序时没有完全匹配”
Re-implementation iOS 13 且没有 SwiftUI
除了 pawello2222 的回答,这里。是我将其命名为 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)
}
}
}
此包装器可以 store/restore 任何类型的可编码 into/from 用户默认值。此外,它适用于 iOS 13,并且 不需要 导入 SwiftUI
.
用法
@UserDefaultStorage(key: "myCustomKey", default: 0)
var myValue: Int
注意不能直接作为State
我们可以通过简单的方法进行测试:
struct Content: View {
private enum Keys {
static let numberOne = "myKey"
}
@AppStorage(Keys.numberOne) var keyValue2: String = "no value"
var body: some View {
VStack {
Button {
keyValue2 = "Hello"
print(
UserDefaults.standard.value(forKey: Keys.numberOne) as! String
)
} label: {
Text("Update")
}
Text(keyValue2)
}
.padding()
.frame(width: 100)
}
}
我试验了一种行为,想与您分享。我有一个视图(称为 MainView),它使用 @AppStorage Bool 来向用户显示或不显示消息。在此视图中,我还有许多视图显示从 JSON(城市)加载的数据。
我开发了一项 'Add to favorite' 功能,该功能使用 UserDefault 来存储用户在 his/her 收藏夹中添加的城市 ID 列表。奇怪的行为是:一旦用户将一个城市添加或删除为收藏夹,所有 MainView 的主体(及其所有 child 视图)都会更新。这意味着所有内容都被无故重新加载。
经过几个小时的调查,特别是重构了我所有的 ObservableObjects(模型)、我的@Published 变量等等,我发现是什么导致了这次大规模更新:我的 MainView 中的@AppStorage!一旦更新 'UserDefaults.standard' 中的任何键(在我的例子中是通过存储我的新收藏夹),所有使用 @AppStorage 的视图的主体都会更新。
所以@AppStorage 很棒而且易于使用。但要注意这种行为。当您设置一个甚至未在该视图中使用的键时,您不希望视图被更新!