Saving Dictionary of Custom Values to User Defaults

I have a custom structure called Place with a few variables such as "Name: String!" and "Description: String!" in there.

I then create a very long dictionary [Int : Place] called PlacesDict in my code with hard-coded values for the keys and values, meaning I enter in all the values in the Place structures in that dictionary.

Then, when I run my app, I have a button. When the button is pressed, it runs the following line of code

UserDefaults.standard.set(placesDict, forKey: "places")

I then get an error in the console that says "

[User Defaults] Attempt to set a non-property-list object { [It prints a portion of my very long dicitonary here] }

libc++abi.dylib: terminating with uncaught exception of type NSException

"

What is going on here and how can I fix it?

Replies

Are you sure there is no typo in the dictionary ?


A way to check is to split the dictionary in 2 parts, at the middle and call the second placesDict2


Then try

UserDefaults.standard.set(placesDict, forKey: "places")
print("firstPart took place")
UserDefaults.standard.set(placesDict2, forKey: "places2")
print("secondPart took place")

And see where you get the error.


Repeat the same split for the part with errors until you get a small enough dict that you can thoroughly inspect.

I split it up many times and in many different ways, but for every single way I would still get the error. I've also used this same dictionary for other things before and it works just fine so I don't think it's a typo.

Could you show some code, notably where you create the dictionary (no need for the full dict, just the beginnig).


Why do you declare optionals (Name: String!) ?


You should declare the dictionary with AnyObject


See here:

h ttps://stackoverflow.com/questions/25463663/store-a-dictionarystring-customobject-in-nsuserdefaults-in-swift

UserDefaults can only store plist objects.


UserDefaults


A default object must be a property list—that is, an instance of (or for collections, a combination of instances of)

NSData
,
NSString
,
NSNumber
,
NSDate
,
NSArray
, or
NSDictionary
. If you want to store any other type of object, you should typically archive it to create an instance of NSData.


In Swift, Data, String, number types and Bool, Date, Array, or Dictionary are bridged to those types, just that (or for collections, a combination of instances of) means all the elements, keys and values needs to to be any of them when Array or Dictionary.


Your custom type `Place` does not seem to be any of them, so you cannot store the `Dictionary<Int, Place>` into UserDefaults.


As noted in the doc, you need to create an instance of Data. Using Codable can be one possible way to do it.


What is going on here and how can I fix it?


You are not showing enough code to show how.



(By the way, generally, UserDefaults is not designed to contain very long objects. If your Dictionary is really very long, there may be a better way to persist it. And you say with hard-coded values, all Dictionary enties are hard-coded and never be updated programatically once created?)

After digging more on the web I found out about this archiving thing as well. Now though I get the error "-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x1c0311310" when I run this code to archive my data:

let archivedObject = NSKeyedArchiver.archivedData(withRootObject: placesDict as NSArray)
        UserDefaults.standard.set(archivedObject, forKey: "places")
        UserDefaults.standard.synchronize()


Also, if I can't get this to work, I might want to look into Core Data. I've never used it before so I wanted to stick with User Defaults for this project but it looks like that might be the only easy way. I would need to put Core Data into my existing project, though, which I haven't found a way to do so far (All the tutorials I've seen say 'check the box that says Core Data when you create your app' but I've already created mine so I don't know what to do there)

Using NSKeyedArchiver is another way to archive your Dictionary into Data, but you get error as you are doing wrong.


You say placeDict is a Dictionary, why do you think `as NSArray` is the right way?

To use NSKeyedArchiver, all values needs to be an `@objc` object correctly conforming to NSCoding protocol.

Again, I cannot show you how, as you are hiding the definition of `Place`.


Also, if I can't get this to work, I might want to look into Core Data.


Do not use UserDefaults just for simplifying your code unless your app is a simple sample code for beginners.

Use UserDefaults when it is appropriate to use.

Anyway, you can use plain files to store Data. I cannot see if Core Data is appropriate or not for your use case.