具有已发布枚举的用户默认值

Posted

技术标签:

【中文标题】具有已发布枚举的用户默认值【英文标题】:Userdefaults with published enum 【发布时间】:2021-02-13 08:14:27 【问题描述】:

尝试保存用户设置,但 UserDefaults 不起作用,Xcode 12.3,swiftui 2.0,当我重新加载我的应用程序时,我的设置没有更新为新值)

 class PrayerTimeViewModel: ObservableObject 
@Published var lm = LocationManager()
@Published var method: CalculationMethod = .dubai 
    didSet 
        UserDefaults.standard.set(method.params, forKey: "method")
        self.getPrayerTime()
    

  
func getPrayerTime() 
    let cal = Calendar(identifier: Calendar.Identifier.gregorian)
    let date = cal.dateComponents([.year, .month, .day], from: Date())
    let coordinates = Coordinates(latitude: lm.location?.latitude ?? 0.0, longitude: lm.location?.longitude ?? 0.0)
    var par = method.params
    par.madhab = mashab
    self.times = PrayerTimes(coordinates: coordinates, date: date, calculationParameters: par)

并查看.. 使用 AppStorage 进行更新

 struct MethodView: View 

@ObservedObject var model: PrayerTimeViewModel
@Environment(\.presentationMode) var presentationMode
@AppStorage("method", store: UserDefaults(suiteName: "method")) var method: CalculationMethod = .dubai
var body: some View 
    List(CalculationMethod.allCases, id: \.self)  item in
        Button(action: 
            self.model.objectWillChange.send()
            self.presentationMode.wrappedValue.dismiss()
            self.model.method = item
            method = item
        ) 
            HStack 
                Text("\(item.rawValue)")
                if model.method == item 
                    Image(systemName: "checkmark")
                        .foregroundColor(.black)
                
            
        
    

【问题讨论】:

你在哪里加载来自UserDefaults的值?您还应该显示CalculationMethod 的代码 我正在使用 AppStorage、checkOut、我的技能 swiftui Junior 更新我的视图)也许我没有取消 你还没有显示CalculationMethod,那是什么?什么是method.params?您还可以从两个不同的 UserDefaults 套件(标准和方法)中存储和检索。 CalculationMethod 是带case的枚举,所以这个枚举里面的params是var,他是切换case并返回一些参数,所以用户只能在SettingView中选择case,但我无法保存)跨度> 您似乎将参数保存在 userdefaults 中,但随后将枚举与您的 AppStorage 属性包装器一起使用。 CalculationMethod 的代码可以吗? 【参考方案1】:

你有两个问题。

首先,正如我在上面的评论中提到的,您正在为UserDefaults 使用两个不同的套件。这意味着您正在从两个不同的位置存储和检索。要么使用UserDefaults.standard,要么使用您选择的套件UserDefaults(suitName: "method") - 您不必使用套件,除非您计划与其他扩展共享您的默认设置,那么这样做是明智的。

其次,您在UserDefaults 中存储了错误的项目。您正在存储计算属性 params 而不是实际的枚举值。当您尝试检索该值时,它会失败,因为它没有得到预期的值并使用您设置的默认值。

这是一个简单的例子,展示了你可以做什么。有一个简单的枚举,它有一个原始值 (String) 并符合 Codable,它还有一个计算属性。这与您的枚举匹配。

我在ObservableObject 中添加了一个初始化程序。这用于在构造 Race 对象时从 UserDefaults 填充我发布的 Place

然后在我的ContentView 中,我根据按下按钮更新place。这会更新 UI 并更新 UserDefaults 中的值。

这应该足以让您了解它的工作原理。

enum Place: String, Codable 
    case first
    case second
    case third
    case notPlaced

    var someComputedProperty: String 
        "Value stored: \(self.rawValue)"
    


class Race: ObservableObject 
    @Published var place: Place = .notPlaced 
        didSet 
            // Store the rawValue of the enum into UserDefaults
            // We can store the actual enum but that requires more code
            UserDefaults.standard.setValue(place.rawValue, forKey: "method")

            // Using a custom suite
            // UserDefaults(suiteName: "method").setValue(place.rawValue, forKey: "method")
        
    

    init() 
        // Load the value from UserDefaults if it exists
        if let rawValue = UserDefaults.standard.string(forKey: "method") 
            // We need to nil-coalesce here as this is a failable initializer
            self.place = Place(rawValue: rawValue) ?? .notPlaced
        

        // Using a custom suite
        // if let rawValue = UserDefaults(suiteName: "method")?.string(forKey: "method") 
        //    self.place = Place(rawValue: rawValue) ?? .notPlaced
        // 
    


struct ContentView: View 
    @StateObject var race: Race = Race()

    var body: some View 
        VStack(spacing: 20) 
            Text(race.place.someComputedProperty)
                .padding(.bottom, 20)
            Button("Set First") 
                race.place = .first
            
            Button("Set Second") 
                race.place = .second
            
            Button("Set Third") 
                race.place = .third
            
        
    


附录:

因为枚举符合Codable,所以可以使用AppStorage 来读取和写入属性。但是,这不会更新您的 ObservableObject 中的值,因此它们很容易不同步。最好有一个可以控制值的地方。在这种情况下,您的ObservableObject 应该是事实的来源,所有更新(读取和写入UserDefaults)都应该通过那里进行。

【讨论】:

【参考方案2】:

您在一个 UserDefaults 域中写入,但从另一个域中读取。假设您的意图是仅使用套件 UserDefaults,您应该在模型中更改一个,例如

@Published var method: CalculationMethod = .dubai 
    didSet 
        UserDefaults(suiteName: "method").set(method.params, forKey: "method")
        self.getPrayerTime()
    

或者如果您想使用标准,那么只需使用带有默认构造函数的 AppStorage,例如

// use UserDefaults.standard by default
@AppStorage("method") var method: CalculationMethod = .dubai

【讨论】:

不工作(尝试查看我的 GitHub,链接在 cmets 中)

以上是关于具有已发布枚举的用户默认值的主要内容,如果未能解决你的问题,请参考以下文章

如何在Django中基于枚举为模型字段设置默认值?

具有符合 CaseIterable、RawRepresentable 的关联值的枚举

如何填充具有默认值的用户定义记录?

如果用户从浏览器返回时具有值,如何启用默认禁用的输入

AutoBeanCodex.encode 省略了具有默认值的属性

Mongoose 阻止将默认值分配给枚举