【问题标题】:SwiftUI: What is @AppStorage property wrapperSwiftUI:什么是@AppStorage 属性包装器
【发布时间】:2020-10-15 03:42:11
【问题描述】:

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

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

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

@AppStorage

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

【问题讨论】:

    标签: ios swift swiftui userdefaults ios14


    【解决方案1】:

    应用存储

    @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 的 answer 中提到的,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))
                }
            }
        }
    }
    

    有用的链接:

    【讨论】:

    • 你知道@AppStorage是否在每次创建包含视图结构时都从磁盘读取?
    • @malhal 不幸的是我不知道,我找不到足够的文档。但是我想以某种方式观察到 UserDefaults 并且在检测到更改时通知 AppStorage 重新加载。因此,当视图被重绘时,它可能不会从磁盘中读取。但这只是猜测。
    • 好的,谢谢,我会运行一些测试来弄清楚。我问的原因是我注意到每次创建视图时@FetchRequest 都会命中数据库,这是一个大问题。
    【解决方案2】:

    免责声明:iOS 14 Beta 2

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

    尝试使用其他类型(如 Array)会导致错误:“No exact matches in call to initializer”

    【讨论】:

    • 您知道在即将到来的测试版中是否会添加数组,或者您知道保存数组的任何解决方法吗?
    • @Lilfaen 您可以将数组编码为字符串表示形式,例如使用 JsonEncoder()。
    • @Lilfaen 我添加了一个如何在my answer 中保存Array 的示例
    【解决方案3】:

    在没有 SwiftUI 的情况下为 iOS 13 重新实现

    除了 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)
            }
        }
    }
    

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

    用法

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

    注意不能直接用作State

    【讨论】:

    • 我知道您提到它目前不能用作@State,但也许您可以以某种方式将其设为DynamicProperty?所以它的行为就像@State? (那就完美了)。
    • 我正在努力 :) 我认为它会有一些不必要的并发症。但我会尝试 :) @pawello2222
    【解决方案4】:

    这是 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会观察到store,SwiftUI视图会自动更新。

    【讨论】:

      【解决方案5】:

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

      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-05-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-31
        • 1970-01-01
        • 1970-01-01
        • 2020-10-05
        • 1970-01-01
        相关资源
        最近更新 更多