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

Get UserDefaults to persist with custom data type

我正在尝试将一个变量设置为 UserDefault,这样当用户打开这个应用程序(显示问题列表)时,他们会得到他们上次 selected 时的任何“包”上次在应用程序中。

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

似乎 defaultPack 变量设置正确,但不能设置 UserDefaults,因为每当我重新打开应用程序时,它都会重置为原始默认“Pack”,而不是用户的默认值最后 selected.

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

这是一种自定义数据类型,因此我使用了 UserDefaults 的扩展,encodes/decodes 该值用于存储。

这是我的 class 和 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 的扩展,其中包含 encodable/decodable 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")** 

前段时间出过一个运行类似的错误。我对 Userdefault 的序列化没有任何错误,但是 @Published 和 didSet 属性 观察器中存在错误。 你真的确定你的 didSet 被调用了吗? 在我的例子中,当我改变我的变量时,它没有触发 didSet 属性 观察者。我最终使用了这里描述的自定义绑定:

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

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

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

@FitzChill,我不知道如何用这个多行代码添加注释,但检查 didSet 帮助我解决了问题。看来我的初始化有问题。

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

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