27

我曾经使用以下语句将重要的应用程序数据(例如登录凭据)保存到 UserDefaults 中:

UserDefaults.standard.set("sample@email.com", forKey: "emailAddress")

现在,我知道 SwiftUI 引入了新的属性包装器,称为:

@AppStorage

谁能解释一下新功能是如何工作的?

4

6 回答 6

37

应用存储

@AppStorage是一种从 UserDefaults 保存和读取变量的便捷方法,并以与@State属性相同的方式使用它们。它可以看作是一个@State自动保存到(和读取)的属性UserDefaults

你可以想到以下几点:

@AppStorage("emailAddress") var emailAddress: String = "sample@email.com"

作为这个的等价物(这在 SwiftUI 中是不允许的,并且不会编译):

@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目前用途有限:

您可以在 @AppStorage 中使用的类型(当前)限于: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))
            }
        }
    }
}

有用的链接:

于 2020-06-24T20:42:51.360 回答
7

免责声明:iOS 14 Beta 2

除了其他有用的答案之外,您可以在 @AppStorage 中使用的类型(当前)仅限于:Bool、Int、Double、String、URL、Data

尝试使用其他类型(例如数组)会导致错误:“调用初始化程序时没有完全匹配”

于 2020-07-11T12:41:56.907 回答
7

iOS 13 和没有 SwiftUI 的重新实现

除了 pawello2222 答案,这里。是我将其命名为 AppStorage 的重新实现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)
        }
    }
}

这个包装器可以将任何类型的可编码存储/恢复到用户默认值中/从用户默认值中恢复。此外,它适用于 iOS 13,不需要导入SwiftUI.

用法

@UserDefaultStorage(key: "myCustomKey", default: 0)
var myValue: Int

请注意,它不能直接用作State

于 2020-10-31T07:56:23.947 回答
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 视图会自动更新。

于 2020-06-26T10:28:15.690 回答
0

我已经尝试了一种我想与你分享的行为。我有一个视图(称为 MainView),它使用 @AppStorage Bool 向用户显示或不显示消息。在这个视图中,我还有许多显示从 JSON(城市)加载的数据的视图。

我开发了一个“添加到收藏夹”功能,它使用 UserDefault 来存储用户在他/她的收藏夹中添加的城市 ID 列表。奇怪的行为是这样的:一旦用户添加或删除了喜爱的城市,所有 MainView 的主体(及其所有子视图)都会更新。这意味着所有内容都无缘无故地重新加载。

经过数小时的调查,特别是重构了我所有的 ObservableObjects(模型)、我的 @Published 变量等等,我发现了是什么造成了这个大规模更新:我的 MainView 中的 @AppStorage!只要您更新“UserDefaults.standard”中的任何键(在我的情况下,通过存储我的新收藏),所有使用 @AppStorage 的视图的主体都会更新。

所以@AppStorage 很棒且易于使用。但请注意这种行为。当您设置一个甚至没有在该视图中使用的键时,您不会期望视图会更新!

于 2022-01-11T17:49:07.830 回答
0

我们可以通过简单的方法对此进行测试:

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)
    }
}
于 2020-12-08T16:40:44.793 回答