【问题标题】:SwiftUI: How to persist @Published variable using UserDefaults?SwiftUI:如何使用 UserDefaults 持久化 @Published 变量?
【发布时间】:2019-12-27 21:57:44
【问题描述】:

我希望保留一个 @Published 变量,以便每次重新启动我的应用程序时它都是相同的。

我想在一个变量上同时使用@UserDefault 和@Published 属性包装器。例如,我需要一个 '@PublishedUserDefault var isLogedIn'。

我有以下propertyWrapper

import Foundation

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T

    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }

    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

这是我的设置类

import SwiftUI
import Combine

 class Settings: ObservableObject {

   @Published var isLogedIn : Bool = false

 func doLogin(params:[String:String]) {

        Webservice().login(params: params) { response in

            if let myresponse = response {                    
                    self.login = myresponse.login
                    }
               }
         }

}

我的视图类

struct HomeView : View {
    @EnvironmentObject var settings: Settings
    var body: some View {
        VStack {
            if settings.isLogedIn {
            Text("Loged in")
            } else{
            Text("Not Loged in")
            }
        }
    }
}

有没有办法制作一个涵盖持久化和发布的单一属性包装器?

【问题讨论】:

    标签: swiftui swift5 xcode11


    【解决方案1】:
    private var cancellables = [String:AnyCancellable]()
    
    extension Published {
        init(wrappedValue defaultValue: Value, key: String) {
            let value = UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
            self.init(initialValue: value)
            cancellables[key] = projectedValue.sink { val in
                UserDefaults.standard.set(val, forKey: key)
            }
        }
    }
    
    class Settings: ObservableObject {
        @Published(key: "isLogedIn") var isLogedIn = false
        ...
    }
    

    示例:https://youtu.be/TXdAg_YvBNE

    所有Codable 类型的版本请查看here

    【讨论】:

    • 避免传入objectWillChange 的麻烦,自定义propertyWrapper 意味着使用自定义初始化,它只使用现有存储并通过接收器在更改时存储在集合上。我没有想到。 :-) 我的解决方案比较麻烦,你必须在使用 propertyWrapper 之前在 init() 中设置 objectWillChange
    • 谢谢你,它让它变得更简单了^_^
    • 谢谢@victor!应该可以采用两种方式,以便用户默认更改更新值。
    • 我对您的回答有一个小问题,在已发布的扩展中添加 Userdefaults 的 Singelton 会导致应用程序保持所有项目都可以访问吗?如果我用钥匙串做同样的事情,它会对性能产生任何影响吗?
    【解决方案2】:

    要保留您的数据,您可以使用@AppStorage 属性包装器。 但是,如果不使用@Published,您的ObservableObject 将不再发布有关更改数据的消息。要解决此问题,只需从属性的 willSet 观察者调用 objectWillChange.send()

    class Settings: ObservableObject {
        @AppStorage("Example") var example: Bool = false {
            willSet {
                // Call objectWillChange manually since @AppStorage is not published
                objectWillChange.send()
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      应该可以组成一个新的属性包装器:

      该提案的第一次修订版中省略了成分, 因为可以手动组合属性包装器类型。例如, 组合 @A @B 可以实现为 AB 包装器:

      @propertyWrapper
      struct AB<Value> {
        private var storage: A<B<Value>>
      
        var wrappedValue: Value {
          get { storage.wrappedValue.wrappedValue }
          set { storage.wrappedValue.wrappedValue = newValue }
        }
      }
      

      这种方法的主要好处是它的可预测性: AB决定如何最好地实现A和B的组合,命名它 适当地,并提供其正确的 API 和文档 语义。另一方面,必须手动写出每个 组合是很多样板文件,特别是对于一个功能 主要卖点是消除样板。也是 不幸的是不得不为每个作品发明名字——当我尝试的时候 通过@A @B 组合A 和B,我怎么知道去寻找 手动组合的属性包装器类型 AB?或者也许应该是 学士?

      参考:Property WrappersProposal: SE-0258

      【讨论】:

        【解决方案4】:

        您目前无法将 @UserDefault 包裹在 @Published 周围,因为目前不允许这样做。

        实现@PublishedUserDefault的方法是将objectWillChange传递到包装器中,并在设置变量之前调用它。

        【讨论】:

        • Property Wrappers 示例struct Field&lt;Value: DatabaseValue&gt;。它采用String 作为参数。您必须改为传递objectWillChange@Published 的唯一特别之处在于它会在适当的时间调用 objectWillChange.send()
        • @Fabian 我在下面发布了我的解决方案
        【解决方案5】:
        struct HomeView : View {
            @StateObject var auth = Auth()
            @AppStorage("username") var username: String = "Anonymous"
        
            var body: some View {
                VStack {
                    if username != "Anonymous" {
                        Text("Logged in")
                    } else{
                        Text("Not Logged in")
                    }
                }
                .onAppear(){
                     auth.login()
                }
            }
        }
        
        
        import SwiftUI
        import Combine
        
        class Auth: ObservableObject {
        
         func login(params:[String:String]) {
        
                Webservice().login(params: params) { response in
        
                    if let myresponse = response {          
                            UserDefaults.standard.set(myresponse.login, forKey: "username")`
                            }
                       }
                 }
        
        }
        

        【讨论】:

          猜你喜欢
          • 2019-12-17
          • 2021-09-30
          • 2021-09-07
          • 2018-12-20
          • 2020-03-27
          • 2011-11-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多