NSInvalidArgumentException on save an array of custom objects in UserDefaults - swift

I would like to save an array of object in my UserDefaults
let thisAction = action(ts: String(Date().timeStamp()), winner: winner)
actions.append(thisAction)
UserDefaults.standard.set(actions, forKey: "currentMatch")
UserDefaults.standard.synchronize()
But I have the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object

Try Objective-C objects (NSArray, NSDictionary).
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/UserDefaults/AccessingPreferenceValues/AccessingPreferenceValues.html#//apple_ref/doc/uid/10000059i-CH3-97383 .
It is stated that objects of types NSData, NSDate, NSString, NSNumber, NSArray, NSDictionary, and NSURL can be saved, got.
So, a possible solution is:
struct Action { var timestamp: TimeInterval; var winner: String }
var actions: [Action] = []
...
actions.map({ ["time": $0.timestamp, "winner": $0.winner ] as NSDictionary })
// You could also convert the actions manually one by one into NSDictionary
// and append them to an [NSDictionary]
UserDefaults.standard.set(actions as NSArray, forKey: "currentMatch")
UserDefaults.standard.synchronize()
Swift arrays and dictionary can be bridged to NSArray, NSDictionary respectively.
You can also use Codable, it can serialize property lists also.

Related

When do we need to use JSONEncoder and JSONDecoder with UserDefaults

To save some data to UserDefaults first we must encode it as JSON using JSONEncoder, which will send back a Data instance we can send straight to UserDefaults.Then reading saved data is a matter of converting from Data using a JSONDecoder. But sometimes we dont have to do that.
My question is will that method work anytime and when do i have to use it because i found this other solution without encode and decode:
var allWords = [String]()
var usedWords = [String]()
var currentWord: String?
In viewDidLoad:
let defaults = UserDefaults.standard
if let presentWord = defaults.object(forKey: "presentWord") as? String,
let savedWords = defaults.object(forKey: "savedWords") as? [String] {
title = presentWord
currentWord = presentWord
usedWords = savedWords
print("Loaded old game!")
Save method:
func save() {
let defaults = UserDefaults.standard
defaults.set(currentWord, forKey: "presentWord")
defaults.set(usedWords, forKey: "savedWords")
}
It's simple and faster way but Im not sure when i can use it with no worries
UserDefaults storage is a property list. NSString, NSData, NSArray, and NSDictionary are the only Cocoa classes that can be expressed directly in a property list. Moreover, an NSArray or NSDictionary can be expressed in a property list only if its elements are instances of those classes, along with NSDate and NSNumber. Those are the property list types.
If your Swift type bridges to a property list type, you can store it directly. So String will bridge to NSString, and an array of String will bridge to an NSArray of NSString, so you can store them directly.
But if what you've got is not a property list type, you need to transform it into a property list type before you can store it, and the usual solution is to transform it into an NSData (Swift Data). You don't have to use JSONEncoder for that but you do need to do it somehow.

NSUser Defaults

I have tried an answer which doesn't work: Swift Saving user NSUser Defaults.
My problem is that i want to save : var myDict = [Int:String]() permanently using NSUser defaults.
My code is :
let userDefaults = NSUserDefaults.standardUserDefaults()
#IBAction func AddOneWord(sender: AnyObject) {
if newWord.text != "" {
myDict.updateValue(newWord.text!, forKey: 1)
self.Word1Dictionnary.text = myDict[1]
userDefaults.setValue(myDict, forKey: "1")
userDefaults.synchronize()
}
}
The problem is that I have this error when clicking on the button on my app (which is running) : Thread 1 : Signal SIGABRT.
NSUserDefaults can only store property-list objects. As noted in the Property List Programming Guide:
And although NSDictionary and CFDictionary objects allow their keys to be objects of any type, if the keys are not string objects, the collections are not property-list objects.
You cannot store an [Int: String] in NSUserDefaults. The key must be a string.

Swift how may I save contacts in defaults

Good morning all, I followed this tutorial
I am able to add a new contact and retrieve existing contacts , but what I am trying to accomplish is save the list that I add in a mutableArray and use that array to populate my TableView. I am getting this error
Contacts Introduction[5166:4971615] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object (
"!$_, value=>\"\n), emailAddresses=(\n \"!$_, value=>\"\n), postalAddresses=(\n)>"
) for key contactsKey'
Here
func insertNewObject(sender: NSNotification) {
print("How Many Times am I getting here ??")
if let contact = sender.userInfo?["contactToAdd"] as? CNContact {
objects.insert(contact, atIndex: 0)
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
print("How Many Objects ",objects.count)
let contactArray = objects as [CNContact]
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setValue(contactArray, forKey: "contactsKey")
}
Any Help is appreciated.
Regards
JZ
NSUserDefaults is backed by a property list, so you can only add objects to it that are property list objects (see list of compatible objects here).
CNContact is not on that list, so you can't add it to NSUserDefaults directly.
One way around this is to serialize/deserialize the CNContact object manually into something compatible like an NSDictionary or NSData (like in this answer) which you can then save to NSUserDefaults.

'-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object' cause crash

I am assigning UserInfo Dictionary from NSUserDefault as NSMutableDictionary.
Now my dicInfo is mutable dictionary, but object it contains are immutable.
So, when i am trying to replace those value it cause crash.
I am attaching image which describe crash report.
If any solution, to how to convert inner object of mutable dictionary to mutable.
Thanks
The NSDictionary class conforms to the NSMutableCopying protocol. As such, we can call the mutableCopy method on an NSDictionary to get an NSMutableDictionary copy of the object.
let dicInfo = userSharedDefaults?.objectForKey(UserDefaultKey.kUserBasicInfo) as? NSDictionary
let mutableDictionary = dicInfo?.mutableCopy
In Swift, we may need to cast this as the correct type:
let mutableDictionary = dicInfo?.mutableCopy as? NSMutableDictionary
var dicInfo = (userSharedDefault.object(forKey: "kUserbasicInfo") as! NSDictionary).mutableCopy() as! NSMutableDictionary
You can also create Mutable Dictionary as follows:
It will fix the crash.
let dicInfo = NSMutableDictionary.init(dictionary: userSharedDefaults?.objectForKey(UserDefaultKey.kUserBasicInfo) as! NSDictionary)
Neither use NSMutableDictionary nor mutableCopy() in Swift to get a mutable dictionary from UserDefaults.
Never do that.
Normally far be it from me to criticize other answers but NSMutableDictionary and mutableCopy() are indeed inappropriate API in Swift.
To get a dictionary from UserDefaults use dedicated method dictionary(forKey:. The default dictionary type is [String:Any]
To make an object mutable simply use the var keyword
var userbasicInfo : [String:Any]
if let dictionary = UserDefaults.standard.dictionary(forKey: UserDefaultKey.kUserBasicInfo) {
userbasicInfo = dictionary
} else {
userbasicInfo = [String:Any]()
}
userbasicInfo[kPin] = 5678
print(userbasicInfo)
UserDefaults.standard.set(userbasicInfo, forKey:UserDefaultKey.kUserBasicInfo)

App Delegate weird error when trying to add element to NSUserDefaults

I've got a really weird error while running my app on Xcode 7 (Swift 2) that shows a "Thread 1: signal SIGABRT" running error message in the App Delegate class of my app. However I've actually already got this "Thread 1: signal SIGABRT" running error message in the App Delegate class lots of times, mainly when deleting an outlet reference in my code and forgetting to also delete it from storyboard. But that's certainly the first time I've got this same error when trying to make the command:
let wasteGain = WastesGainsClass(value: enteredMoney, originOrCat: segControlArray[segControl.selectedSegmentIndex], specification: plusEspecTField.text!, date: dateArray, mode: "gain")
gains.append(wasteGain)
NSUserDefaults.standardUserDefaults().setObject(gains, forKey: "gains")
What happens is that if I just comment the line NSUserDefaults.standardUserDefaults().setObject(gains, forKey: "gains") the app doesn't crash! So the error might just be in that line.
If anyone could help me, I`d thank you so much.
PS: WastesGainsClass format is like this:
class WastesGainsClass {
var value:Int = 0
var origin:String
var specification:String
var date:[String]
var mode:String
var rowMode:Int = 0
init(value:Int, originOrCat:String, specification:String, date:[String], mode:String) {
self.value = value
self.origin = originOrCat
self.specification = specification
self.date = date
self.mode = mode
}
}
From documentation:
The NSUserDefaults class provides convenience methods for accessing
common types such as floats, doubles, integers, Booleans, and URLs. 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 you can also use:
Int, UInt, Double, Float and Bool types because they are automatically bridged to NSNumber;
String bridged to NSString
[AnyObject] because it is bridged to NSArray;
[NSObject: AnyObject] because it is bridged to NSDictionary.
Of course type of array elements and dictionary values must be one of above types. Dictionary key type must be NSString (or bridged String).
To store instances of any other class you have two options:
Your custom class must be subclass of NSObject and conform to
NSCoding protocol and then you can archive object of this class to NSData with NSKeyedArchiver.archivedDataWithRootObject() and save it to NSUserDefaults and later retrieve it from NSUserDefaults and unarchive with NSKeyedUnarchiver.unarchiveObjectWithData():
import Foundation
class WastesGainsClass: NSObject, NSCoding {
var value: Int
init(value: Int) {
self.value = value
}
required init(coder aDecoder: NSCoder) {
value = aDecoder.decodeObjectForKey("value") as! Int
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(value, forKey: "value")
}
}
var gains = [WastesGainsClass(value: 1), WastesGainsClass(value: 2)]
NSUserDefaults.standardUserDefaults().setObject(gains.map { NSKeyedArchiver.archivedDataWithRootObject($0) }, forKey: "gains")
if let gainsData = NSUserDefaults.standardUserDefaults().objectForKey("gains") as? [NSData] {
gains = gainsData.map { NSKeyedUnarchiver.unarchiveObjectWithData($0) as! WastesGainsClass }
}
You can save your custom object properties to dictionary and store that
dictionary in NSUserDefaults:
import Foundation
class WastesGainsClass {
var value: Int
init(value: Int) {
self.value = value
}
}
extension WastesGainsClass {
convenience init(dict: [NSObject: AnyObject]) {
self.init(value: dict["value"] as? Int ?? 0)
}
func toDict() -> [NSObject: AnyObject] {
var d = [NSObject: AnyObject]()
d["value"] = value
return d
}
}
var gains = [WastesGainsClass(value: 1), WastesGainsClass(value: 2)]
NSUserDefaults.standardUserDefaults().setObject(gains.map { $0.toDict() }, forKey: "gains")
if let dicts = NSUserDefaults.standardUserDefaults().objectForKey("gains") as? [[NSObject: AnyObject]] {
gains = dicts.map { WastesGainsClass(dict: $0) }
}
NSUserDefaults unfortunately can't accept arbitrary objects, only objects that can be encoded in a Property List. See Apple's reference guide for Property Lists to learn which objects can be stored.
If you need to save several WastesGainsClass objects, you may wish to write a method that returns a Dictionary encoding their Property List-representable properties, and an initializer that accepts such a Dictionary to restore the object.
However, if you truly need to save multiple custom objects like this, you probably don't want to use NSUserDefaults at all. Consider a document-based app, and look into NSCoding.
The code you posted tries to save an array of custom objects to NSUserDefaults. You can't do that. Implementing the NSCoding methods doesn't help. You can only store things like NSArray, NSDictionary, NSString, NSData, NSNumber, and NSDate in NSUserDefaults.
You need to convert the object to NSData (like you have in some of the code) and store that NSData in NSUserDefaults. You can even store an NSArray of NSData if you need to.
see this post : Attempt to set a non-property-list object as an NSUserDefaults