Retrieving array of custom objects from UserDefaults using NSKeyedUnarchiever in swift - 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?

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.

Saving and retrieving a struct which is not codable to Userdefaults in Swift

I want to save a struct which can not be modified to UserDefaults in my code. I went through a number of solutions but everyone is advising to use Codable/NSCoding in the struct. I can not edit the struct. Does anyone have an idea how we can achieve this.
I tried to wrap the object to an NSDictionary but while trying to save, it crashes saying 'Attempt to insert non-property list object'. Is it because I have nil values in my model?
Any help is appreciated.
If you can't change your struct, then create an extension for it and conform it to Codable.
Once conformed to Codable, you can use JSONEncoder and JSONDecoder to convert to and from data and then save/retrieve from UserDefaults.
struct Sample {
var name = "Sample Struct"
}
extension Sample: Codable {
}
let obj = Sample()
let data = try? JSONEncoder().encode(obj)
//Saving in UserDefaults
UserDefaults.standard.set(data, forKey: "SampleStruct")
//Fetching from UserDefaults
if let data = UserDefaults.standard.data(forKey: "SampleStruct") {
let val = try? JSONDecoder().decode(Sample.self, from: data)
print(val) //Sample(name: "Sample Struct")
}
I tried to wrap the object to an NSDictionary
You can't save a custom model without conforming to Codable/NSCoding , if you don't need to conform then save it as a usual Array/Dictionary without wrapping any custom objects inside

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

How to Store Data Sent back using the delegate method?

I have data sent back to my VC using the delegate method. How can I possibly store it so then I can use it to send to another VC?
func DataToPass(ArrayName: [String]) { //function from delegate
Datacollect = ArrayName
print(ArrayName)
}
Here's the function used in the delegate method that holds my data. ArrayName is an array containing my data.
Datacollect is an attempt to collect it, however nothing gets stored in Datacollect.
I have already assigned Datacollect as a String array.
var Datacollect = [String]()
How can I store the data to my VC from ArrayName?
There are lot of ways of storing data and one of the simplest ways to get started is to use the built-in UserDefaults.
This is how you might use the following code inside a method to store your DataCollect array.
Let defaults = UserDefaults.standard
defaults.set(DataCollect, forKey: "DataCollect")
To retrieve the data you could use the following code inside a method:
let defaults = UserDefaults.standard
let DataCollect = defaults.array(forkey: "DataCollect")

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