sunshinejr / SwiftyUserDefaults

Modern Swift API for NSUserDefaults
http://radex.io/swift/nsuserdefaults/static
MIT License
4.85k stars 365 forks source link

Attempt to insert non-property list object #243

Open pepejeria opened 4 years ago

pepejeria commented 4 years ago

I just updated to version 5 from version 3 and I'm getting an "Attempt to insert non-property list object"-error.

Code used in version 3:

extension DefaultsKeys {
    static let userColors = DefaultsKey<[String: UIColor]>("userColors")
}

extension UserDefaults {

    subscript(key: DefaultsKey<[String: UIColor]>) -> [String: UIColor] {
        get { return unarchive(key) ?? [:] }
        set { archive(key, newValue) }
    }

}

Code used for version 5:

extension UIColor: DefaultsSerializable {}

extension DefaultsKeys {
    var userColors: DefaultsKey<[String: UIColor]> { .init("userColors", defaultValue: [:]) }
}

This gives me the error mentioned above though, am I missing something?

sunshinejr commented 4 years ago

Hey @pepejeria, we do not support dictionary objects other than the [String: WhateverIsAbleToBeStoredByPlists]. So basically UIColor is not ready yet to be stored in a dictionary in User Defaults. There is a ground work for it being implemented there, but it's not implemented yet. It would require checking the type of the value and use serializing (similar to how we observe keys). Though I'm not sure if it will be implemented soon since I don't have much time for new additions to the library - maybe you would like to contribute?

For now you can create a custom object that holds the dictionary and specify archive bridges for encoding/decoding the type.

pepejeria commented 4 years ago

I created a custom object that holds the dictionary, based on the example provided in the documentation:

struct UserColors: DefaultsSerializable {

    static var _defaults: DefaultsBridge<UserColors> { DefaultsKeyedArchiverBridge() }
    static var _defaultsArray: DefaultsBridge<[UserColors]> { DefaultsKeyedArchiverBridge() }

    let colorsDic: [String: UIColor] = [:]
}

But this won't compile, nor does the FrogCustomSerializable example compile.

sunshinejr commented 4 years ago

@pepejeria what’s the compile error?

pepejeria commented 4 years ago
Screen Shot 2020-04-01 at 22 13 34
sunshinejr commented 4 years ago

@pepejeria ah sorry about that, seems like some parts of the docs are not updated. Here’s a source code for one of our test cases, you would specify a specific type and return it like that: https://github.com/sunshinejr/SwiftyUserDefaults/blob/38441153885b660da10d3ff742b7e4fe8e8f11cc/Tests/SwiftyUserDefaultsTests/TestHelpers/TestHelper.swift#L106

Also here’s a archive bridge you would probably use for your type.

Let me know if it helps.

MaxHasADHD commented 4 years ago

How would I be able to support something like this? (I'm upgrading from version 3)

subscript(key: DefaultsKey<[CKRecordZone.ID: CKServerChangeToken?]>) -> [CKRecordZone.ID: CKServerChangeToken?]  {
        get { return unarchive(key) ?? [:] }
        set { archive(key, newValue) }
    }

Would it look something like this?

struct CKServerchangeTokenSerializable: DefaultsSerializable {
    static var _defaults: DefaultsKeyedArchiverBridge<[CKRecord.ID: CKServerChangeToken?]> { return DefaultsKeyedArchiverBridge() }
    static var _defaultsArray: DefaultsKeyedArchiverBridge<[[CKRecord.ID: CKServerChangeToken?]]> { return DefaultsKeyedArchiverBridge() }

    let changeTokens: [CKRecord.ID: CKServerChangeToken] = [:]
}

and instead of accessing it like a dictionary from user defaults, It would be like Defaults[\.serverChangeTokens].changeTokens[record.ID]?

sunshinejr commented 4 years ago

hey @MaxHasADHD, if you used archiving then yes, you could use the DefaultsKeyedArchiverBridge 👍 in terms of usage you're correct (given . serverChangeTokens is a DefaultsKey<CKServerchangeTokenSerializable>), depending on your Swift version you could also try using Defaults.serverChangeTokens since we have the support for this syntax too.

williamso1234 commented 1 year ago

import UIKit

class ViewController: UIViewController {

@IBOutlet var notesTextView: UITextView!

@IBOutlet var titleTextField: UITextField!

override func viewDidLoad() {

    super.viewDidLoad()
}

@IBAction func saveButton(_ sender: Any) {

    UserDefaults.standard.set(titleTextField, forKey: "Title")
    UserDefaults.standard.set(notesTextView, forKey: "Body")

}

@IBAction func loadNote(_ sender: Any) {

    let title = titleTextField.text

    print("my title is: \(title!)")

    let notes = notesTextView.text
    print("my body is: \(notes!)")

    titleTextField.text =
        UserDefaults.standard.object(forKey:
        "Title") as? String

    notesTextView.text =
        UserDefaults.standard.object(forKey:
        "Body") as? String

}

} i am having the same issue please help