NSUser Defaults - swift

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.

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.

Retrieving array of custom objects from UserDefaults using NSKeyedUnarchiever in swift

I have saved an array of custom objects that conforms to NSCoding protocol in UserDefaults using NSKeyedArchiver. But when I try to retrieve it using NSKeyedUnarchiver, i get runtime errors from Xcode. I have tried initialising NSArray using unarchived data but it also failed. My guess is that while unarchiving swift doesn't understand custom elements of this array. How shall I do it?
This is how I archived array of custom objects
static func saveCategoryList(_ categoryList : [Category]!) -> Void{
let userDefaults = UserDefaults.standard
let categoryListData = NSKeyedArchiver.archivedData(withRootObject: categoryList)
userDefaults.set(categoryListData, forKey: Constants.CategoryList)
userDefaults.synchronize()
}
I get error like the attached screenshot in runtime. I am quite sure I am not doing it right. How can get my desired result?

How to unarchive data in swift 3?

I have an app where I save an array of CNContact to UserDefaults as such:
var contactsArray = [CNContact]()
let defaults = UserDefaults()
func (contact: CNContact){
contactsArray.append(contact)
let contactArrayArchive = NSKeyedArchiver.archivedData(withRootObject: contactArray) //archive the data
defaults.set(contactArrayArchive, forKey: "contactArray")
defaults.synchronize()
}
This archives the data to allow it to be saved to defaults. My issue is with how I can convert this data back to an array of CNContact in the viewDidLoad. I have seen many answers online which suggest to use NSKeyedArchiver.unarchiveObjectwithData but typing this into Xcode, with swift 3, says that the .unarchiveObjectwithData is not a member of NSKeyedArchiver. I keep looking for stuff about how to do this in swift 3 but have been unsuccessful. How can I unarchive the value?
You can use
NSKeyedUnarchiver.unarchiveObject(with: Data)
You have Used NSKeyedArchiver . You cannot use this to unarchive an Object.
If you press ⇧⌘0 to open the documentation and type nsk (3 characters are sufficient) you will see
To unarchive something you need the NSKeyedUnarchiver

'-[__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