获取 UserDefaults 以保留自定义数据类型

Posted

技术标签:

【中文标题】获取 UserDefaults 以保留自定义数据类型【英文标题】:Get UserDefaults to persist with custom data type 【发布时间】:2020-06-29 01:25:41 【问题描述】:

我正在尝试将变量设置为 UserDefault,以便当用户打开此应用程序(显示问题列表)时,他们会获得上次在应用程序中选择的任何“Pack”。

Pack 由设置为 UserDefaults 的“defaultPack”变量确定,并且该包的数据显示在启动屏幕上。有一个菜单,用户可以在其中选择另一个包,并且那里的链接有一个 @Binding 变量,用于更新主屏幕中的包,它应该返回主屏幕,然后也更新 UserDefaults。

似乎 defaultPack 变量设置正确,但不得设置 UserDefaults,因为每当我重新打开应用程序时,它都会重置为原始默认“Pack”,而不是用户上次选择的那个。

关于我需要更改哪些内容才能正确更新 UserDefaults 有什么想法吗?

它是一种自定义数据类型,因此我使用了 UserDefaults 的扩展,它对该值进行编码/解码以进行存储。

这是我的带有 defaultPack 变量的类:

final class UserInformation: ObservableObject 
    @Published var defaultPack: Pack 
        didSet 
            do 
                try UserDefaults.standard.setObject(defaultPack, forKey: "defaultPack")
             catch 
                print(error.localizedDescription)
            
        
    
    init() 
        self.defaultPack = UserDefaults.standard.object(forKey: "defaultPack") as? Pack ?? samplePack
    

这里是 UserDefaults 的扩展,其中包含 UserDefaults 的可编码/可解码指令

//extension to UserDefaults to include the encodable/decodable instructions for UserDefaults
extension UserDefaults: ObjectSavable 
    func setObject<Object>(_ object: Object, forKey: String) throws where Object: Encodable 
        let encoder = JSONEncoder()
        do 
            let data = try encoder.encode(object)
            set(data, forKey: forKey)
         catch 
            throw ObjectSavableError.unableToEncode
        
    
    
    func getObject<Object>(forKey: String, castTo type: Object.Type) throws -> Object where Object: Decodable 
        guard let data = data(forKey: forKey) else  throw ObjectSavableError.noValue 
        let decoder = JSONDecoder()
        do 
            let object = try decoder.decode(type, from: data)
            return object
         catch 
            throw ObjectSavableError.unableToDecode
        
    

这是我获取默认包的视图:

struct PackTopics: View 
    @ObservedObject var userInformation: UserInformation
    @State var showPacksList: Bool = false

    var packsButton: some View 
        Button(action:  self.showPacksList.toggle()
        ) 
            Image(systemName: "tray.full")
                .imageScale(.large)
                .accessibility(label: Text("Show Packs List"))
                .padding()
        
    
    
    var body: some View 
        NavigationView 
            List 
                ForEach(userInformation.defaultPack.sections, id: \.self)  section in
                    Section(header: SectionHeader(linkedSection: section))
                    
                        ForEach (section.topics, id: \.self)  topic in
                            TopicLine(linkedTopic: topic)
                        
                    
                
            
            .navigationBarTitle(Text(userInformation.defaultPack.name))
            .navigationBarItems(trailing: packsButton)
            .sheet(isPresented: $showPacksList) 
                PackPage(isPresented: self.$showPacksList, newPack: self.$userInformation.defaultPack)
            
            
        
    

这里是绑定到前一个视图中的pack变量的视图,应该更新defaultPack:

struct PackPage: View 
    @Binding var isPresented: Bool
    @Binding var newPack: Pack
    let userDefaults = UserDefaults.standard

    var body: some View 
        VStack 
            HStack 
                Text("Question Packs")
                    .font(.largeTitle)
                    .fontWeight(.bold)
                    .padding()
                Spacer()
            
            Text("Available Packs")
                List 
                    HStack(alignment: .top) 
                        ForEach(purchasedPacks, id: \.self)  pack in
                            UnlockedPackTile(tilePack: pack)
                                .onTapGesture 
                                    print("Originally tapped \(pack.name)")
                                    self.newPack = pack
                                    do 
                                        let userDefaultPack = try self.userDefaults.getObject(forKey: "defaultPack", castTo: Pack.self)
                                        print("Default pack is now set to \(userDefaultPack)")
                                     catch 
                                        print(error.localizedDescription)
                                    
                                    self.isPresented.toggle()
                            
                        
                    
                

添加输出 *

2020-06-30 07:04:06.608783-0500 Travel Conversation Starters[2524:131054] [Agent] Received remote injection
2020-06-30 07:04:06.609182-0500 Travel Conversation Starters[2524:131054] [Agent] Create remote injection Mach transport: 6000023ca0d0
2020-06-30 07:04:06.609622-0500 Travel Conversation Starters[2524:131000] [Agent] No global connection handler, using shared user agent
2020-06-30 07:04:06.609840-0500 Travel Conversation Starters[2524:131000] [Agent] Received connection, creating agent
2020-06-30 07:04:07.916795-0500 Travel Conversation Starters[2524:131000] [Agent] Received message: < DTXMessage 0x600002cc4c00 : i2.0e c0 object:(__NSDictionaryI*) 
    "products" : <NSArray 0x600000acc200 | 1 objects>
    "id" : [0]
    "scaleFactorHint" : [2]
    "providerName" : "28Travel_Conversation_Starters20ContentView_PreviewsV"
    "updates" : <NSArray 0x7fff8062d430 | 0 objects>
 > 
    "serviceCommand" : "forwardMessage"
    "type" : "display"

2020-06-30 07:04:16.447421-0500 Travel Conversation Starters[2524:131000] [Common] _BSMachError: port 9f2f; (os/kern) invalid capability (0x14) "Unable to insert COPY_SEND"
**Originally tapped Family Topics**
**Default pack is now set to Pack(id: 3541AF19-8CDB-4D9B-B261-7194DDD8516E, name: "Family Topics")** 

【问题讨论】:

你能粘贴你的输出吗?也许其中一种方法会引发一些异常。并将UserDefaults.standard.object(forKey: "defaultPack") 的返回值粘贴到您的init 方法中。 更新为包含包含 UserDefaults 返回值的输出。 【参考方案1】:

前段时间遇到了类似的错误。我对 Userdefault 的序列化没有任何错误,但 @Published 和 didSet 属性观察器中存在错误。 你真的确定你的 didSet 被调用了。 在我的情况下,当我更改变量时,它没有触发 didSet 属性观察器。我最终使用了这里描述的自定义绑定:

https://www.hackingwithswift.com/books/ios-swiftui/creating-custom-bindings-in-swiftui.

您也可以尝试将您的对象复制到一个临时变量中并再次将其重置以查看是否调用了 didSet。

我知道这听起来像是一种变通方法,而不是正确的解决方案。但这是了解这里发生的事情的开始。

【讨论】:

我可能会检查一下 - 我几乎可以肯定我的 didSet 正在被调用,因为我有一个打印命令进一步打印该键的 UserDefaults 的值,并且它正在打印正确的值。您没有看到我的 UserDefaults 初始化有任何问题吗?我想知道这或者这是我的应用程序的示例构建这一事实是否会导致 UserDefaults 出现问题。 我也没有在 Info.plist 上专门为这个键创建任何东西,我不确定这对于 UserDefaults 是否有必要(我确信这些听起来像基本问题,但我很新在此)。【参考方案2】:

@FitzChill,我不知道如何使用此多行代码添加注释,但检查 didSet 帮助我排除故障。我的 init 似乎有问题。

我将其更改为 do-catch 闭包,现在似乎可以正常工作了。

    init() 
        do 
            self.defaultPack = try UserDefaults.standard.getObject(forKey: "defaultPack", castTo: Pack.self)
         catch 
            print(error.localizedDescription)
            self.defaultPack = samplePack
        
    

【讨论】:

很高兴听到您找到了解决问题的方法。您确实没有从 UserDefault 正确加载您的对象,顺便说一句,您不需要 Info.plist 的任何权限即可访问标准对象。

以上是关于获取 UserDefaults 以保留自定义数据类型的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用 NSKeyedArchiver 在 UserDefaults 中保存自定义对象

UserDefaults 自定义对象的自定义对象

在 UserDefaults 中存储自定义对象数组

如何在 UserDefaults 中存储自定义对象?

如何在 swift 4 中的 UserDefalts 中设置自定义类数组数据

设置模型类和自定义适配器以从 onListItemClick 获取 SQLite DB rowID