I have an [Int:Bool]
dictionary and I am trying to save it into my NSDictionary.. However, it crashes with error Attempt to set a non-property-list object
let dictionary = [Int:Bool]()
self.dictionary[2] = true
self.dictionary[3] = false
NSUserDefaults.standardUserDefaults().setObject(dictionary, forKey: "dictionary")
Also, for loading, first I tried this but error logged it strictly requires AnyObject?.
NSUserDefaults.standardUserDefaults().objectForKey("dictionary")
Then I tried this and it logged:
NSUserDefaults.standardUserDefaults().objectForKey("dictionary") as? [Int:Bool]
I also tried dictionaryForKey
. I got..
NSUserDefaults.standardUserDefaults().dictionaryForKey("dictionary")
Cannot assign value to type [String: AnyObject] to type [Int:Bool]
So which one of these 2 is a better approach to take? (The values would be optional in my case I think)
1.
NSUserDefaults.standardUserDefaults().objectForKey("dictionary") as? [Int:Bool] ?? [Int:Bool]()
2.
NSUserDefaults.standardUserDefaults().objectForKey("dictionary") as? [Int:Bool])!
Swift 4
Among basic types, UserDefaults
can save any object that conforms to Codable
protocol. Dictionary
is one of the types that implements this protocol. You don't even need to write any custom code:
let dictionary = ["name": "Adam"]
// Save to User Defaults
UserDefaults.standard.set(dictionary, forKey: "names")
// Read from User Defaults
let saved = UserDefaults.standard.value(forKey: "names") as? [String: String]
See more info about Codable
Swift 3
You can use UserDefaults
to save a Dictionary
as long as key and value types are types that can be represented in a plist format (NSNumber
, Data
, etc.). If that's not the case, we can always serialise other types to Data
when writing and deserialise from Data
when reading. It can be accomplished with pretty simple extension of UserDefaults
using NSKeyArchiver
:
extension UserDefaults {
/// Save dictionary on key
open func set<Key, Value>(dictionary: [Key: Value]?, forKey key: String) {
let data = NSKeyedArchiver.archivedData(withRootObject: dictionary as Any)
set(data, forKey: key)
}
// Retrieve dictionary for key
open func dictionary<Key, Value>(forKey key: String) -> [Key: Value]? {
guard let data = object(forKey: key) as? Data else { return nil }
return NSKeyedUnarchiver.unarchiveObject(with: data) as? [Key: Value]
}
}
Now you can call these methods:
let ages = ["Adam": 25]
// Save
UserDefaults.standard.set(dictionary: ages, forKey: "ages")
// Read
let saved: [String: Int]? = UserDefaults.standard.dictionary(forKey: "ages")
print(saved) // Optional(["Adam": 25])
Swift 2
Save custom data
func setCustomDictionary(dict: [Int: Bool]) {
let keyedArch = NSKeyedArchiver.archivedDataWithRootObject(dict)
NSUserDefaults.standardUserDefaults().setObject(keyedArch, forKey: "dictionary")
}
Retrieve data
func getDictionary() -> [Int: Bool]? {
let data = NSUserDefaults.standardUserDefaults().objectForKey("dict")
let object = NSKeyedUnarchiver.unarchiveObjectWithData(data as! NSData)
return object as? [Int: Bool]
}
Usage
var customDictionary = [Int: Bool]()
customDictionary[2] = true
customDictionary[3] = false
// Store data in NSUserDefaults
setCustomDictionary(customDictionary)
// Get data from NSUserDefaults
let userDefaultsDictionary = getDictionary()
I had a similar issue, but with different types of data.
My suggestion is to convert to NSData and retrieve the data like so:
/// Save
NSUserDefaults.standardUserDefaults().setObject(NSKeyedArchiver.archivedDataWithRootObject(object), forKey: key)
/// Read
var data = NSUserDefaults.standardUserDefaults().objectForKey(key) as NSData
var object = NSKeyedUnarchiver.unarchiveObjectWithData(data) as [String: String]
(Although it is mentioned [String : String] I actually used for [[String: AnyObject]] and worked, so maybe it can work for you too!)
This is for Swift 3
func setCustomDictionary(dict: [Int: Bool]) {
let keyedArch = NSKeyedArchiver.archivedData(withRootObject: dict)
UserDefaults.standard.set(keyedArch, forKey: "dictionary")
}
func getDictionary() -> [Int: Bool]? {
let data = UserDefaults.standard.object(forKey: "dict")
let object = NSKeyedUnarchiver.unarchiveObject(with: (data as! NSData) as Data)
return object as? [Int: Bool]
}
if you neeed more types you can use generics like this:
func saveUserDefaults<T>(withKey key: String, dict: AnyObject, myType: T.Type) {
guard let dict = dict as? T else {
print("Type mismatch")
return
}
let archiver = NSKeyedArchiver.archivedData(withRootObject: dict)
UserDefaults.standard.set(archiver, forKey: key)
}
func getUserDefaults<T>(withKey key: String, myType: T.Type) -> T? {
let unarchivedObject = getUserDefaultData(withKey: key)
return unarchivedObject as? T
}
func getUserDefaultData(withKey key: String) -> Any? {
guard let data = UserDefaults.standard.object(forKey: key) else {
return nil
}
guard let retrievedData = data as? Data else {
return nil
}
return NSKeyedUnarchiver.unarchiveObject(with: retrievedData)
}
For example [Int:Int] type usage:
var customDictionary = [Int: Int]()
customDictionary[234] = 1
customDictionary[24] = 2
customDictionary[345] = 3
saveUserDefaults(withKey: "hello", dict: customDictionary as AnyObject, myType: [Int: Int].self)
let savedDictionary = getUserDefaults(withKey: "hello", myType: [Int: Int].self)
print(savedDictionary)