How to Archive and Unarchive Custom Objects in Swift? or How to Save Custom Object to Nsuserdefaults in Swift

How to archive and unarchive custom objects in Swift? Or how to save custom object to NSUserDefaults in Swift?

NSKeyedArchiver will only work with Objective-C classes, not pure Swift classes. You can bridge your class to Objective-C by marking it with the @objc attribute or by inheriting from an Objective-C class such as NSObject.

See Using Swift with Cocoa and Objective-C for more information.

Save custom objects into NSUserDefaults

Actually, you will need to archive the custom object into NSData then save it to user defaults and retrieve it from user defaults and unarchive it again.
You can archive it like this

let teams = [Team(id: 1, name: "team1", shortname: "t1"), Team(id: 2, name: "team2", shortname: "t2")]

var userDefaults = UserDefaults.standard
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: teams)
userDefaults.set(encodedData, forKey: "teams")

and unarchive it like this

let decoded  = userDefaults.data(forKey: "teams")
let decodedTeams = NSKeyedUnarchiver.unarchiveObject(with: decoded) as! [Team]

But if you just did that you will get

.Team encodeWithCoder:]: unrecognized selector sent to instance

You will have to make Team conform to NSCoding just like this

class Team: NSObject, NSCoding {
var id: Int
var name: String
var shortname: String

init(id: Int, name: String, shortname: String) {
self.id = id
self.name = name
self.shortname = shortname

}

required convenience init(coder aDecoder: NSCoder) {
let id = aDecoder.decodeInteger(forKey: "id")
let name = aDecoder.decodeObject(forKey: "name") as! String
let shortname = aDecoder.decodeObject(forKey: "shortname") as! String
self.init(id: id, name: name, shortname: shortname)
}

func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(name, forKey: "name")
aCoder.encode(shortname, forKey: "shortname")
}
}

Swift saving and retrieving custom object from UserDefaults

Swift 4 or later

You can once again save/test your values in a Playground


UserDefaults need to be tested in a real project. Note: No need to force synchronize. If you want to test the coding/decoding in a playground you can save the data to a plist file in the document directory using the keyed archiver. You need also to fix some issues in your class:



class Person: NSObject, NSCoding {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
required init(coder decoder: NSCoder) {
self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
self.age = decoder.decodeInteger(forKey: "age")
}
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
coder.encode(age, forKey: "age")
}
}

Testing:

class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

do {
// setting a value for a key
let newPerson = Person(name: "Joe", age: 10)
var people = [Person]()
people.append(newPerson)
let encodedData = try NSKeyedArchiver.archivedData(withRootObject: people, requiringSecureCoding: false)
UserDefaults.standard.set(encodedData, forKey: "people")
// retrieving a value for a key
if let data = UserDefaults.standard.data(forKey: "people"),
let myPeopleList = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [Person] {
myPeopleList.forEach({print($0.name, $0.age)}) // Joe 10
}
} catch {
print(error)
}

}
}

How to store custom objects in NSUserDefaults

On your Player class, implement the following two methods (substituting calls to encodeObject with something relevant to your own object):

- (void)encodeWithCoder:(NSCoder *)encoder {
//Encode properties, other class variables, etc
[encoder encodeObject:self.question forKey:@"question"];
[encoder encodeObject:self.categoryName forKey:@"category"];
[encoder encodeObject:self.subCategoryName forKey:@"subcategory"];
}

- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
//decode properties, other class vars
self.question = [decoder decodeObjectForKey:@"question"];
self.categoryName = [decoder decodeObjectForKey:@"category"];
self.subCategoryName = [decoder decodeObjectForKey:@"subcategory"];
}
return self;
}

Reading and writing from NSUserDefaults:

- (void)saveCustomObject:(MyObject *)object key:(NSString *)key {
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:encodedObject forKey:key];
[defaults synchronize];

}

- (MyObject *)loadCustomObjectWithKey:(NSString *)key {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *encodedObject = [defaults objectForKey:key];
MyObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
return object;
}

Code shamelessly borrowed from: saving class in nsuserdefaults

How can I store an array of custom objects (Goals)

I am posting code from a learning project I did to store objects using NSCoding. Fully functional and ready to use. A math game that was storing game variables, etc.

//********This class creates the object and properties to store********
import Foundation
class ButtonStates: NSObject {

var sign: String = "+"
var level: Int = 1
var problems: Int = 10
var time: Int = 30
var skipWrongAnswers = true

func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeObject(sign, forKey: "sign")
aCoder.encodeInteger(level, forKey: "level")
aCoder.encodeInteger(problems, forKey: "problems")
aCoder.encodeInteger(time, forKey: "time")
aCoder.encodeBool(skipWrongAnswers, forKey: "skipWrongAnswers")
}

init(coder aDecoder: NSCoder!) {
sign = aDecoder.decodeObjectForKey("sign") as String
level = aDecoder.decodeIntegerForKey("level")
problems = aDecoder.decodeIntegerForKey("problems")
time = aDecoder.decodeIntegerForKey("time")
skipWrongAnswers = aDecoder.decodeBoolForKey("skipWrongAnswers")
}

override init() {
}
}

//********Here is the data archiving and retrieving class********
class ArchiveButtonStates:NSObject {

var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""

func ArchiveButtons(#buttonStates: ButtonStates) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("buttonStates.archive")

if NSKeyedArchiver.archiveRootObject(buttonStates, toFile: path) {
//println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}

func RetrieveButtons() -> NSObject {
var dataToRetrieve = ButtonStates()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("buttonStates.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? ButtonStates {
dataToRetrieve = dataToRetrieve2 as ButtonStates
}
return(dataToRetrieve)
}
}

the following is in my ViewController where the game is played. Only showing the relevant code for retrieving and storing objects

class mathGame: UIViewController {

var buttonStates = ButtonStates()

override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//set inital view

//retrieving a stored object & placing property into local class variables
buttonStates = ArchiveButtonStates().RetrieveButtons() as ButtonStates
gameData.sign = buttonStates.sign
gameData.level = buttonStates.level
gameData.problems = buttonStates.problems
gameData.time = buttonStates.time

}

override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)

//storing the object
ArchiveButtonStates().ArchiveButtons(buttonStates: buttonStates)
}
}

Swift 4: Save and retrieve an array of custom objects (with nested custom objects) to UserDefaults

Go ahead and implement NSCoding for the other two custom objects. Also, change your decodeObject to decodeInteger on all Integers of your custom objects, and remove the "as! Int" from them. Then, do this:

let userDefaults = UserDefaults.standard
let encodedData = NSKeyedArchiver.archivedData(withRootObject: self.saleArray)
userDefaults.set(encodedData, forKey: "sales")

To retrieve the data, do this:

let newArray = NSKeyedUnarchiver.unarchiveObject(with: data as! Data) as! [SaleObject]

After you have it working, go back and research Codable. Enjoy!

How to save an array of objects to NSUserDefault with swift?

From the Property List Programming Guide:

If a property-list object is a container (that is, an array or dictionary), all objects contained within it must also be property-list objects. If an array or dictionary contains objects that are not property-list objects, then you cannot save and restore the hierarchy of data using the various property-list methods and functions.

You'll need to convert the object to and from an NSData instance using NSKeyedArchiver and NSKeyedUnarchiver.

For example:

func savePlaces(){
let placesArray = [Place(lat: 123, lng: 123, name: "hi")]
let placesData = NSKeyedArchiver.archivedDataWithRootObject(placesArray)
NSUserDefaults.standardUserDefaults().setObject(placesData, forKey: "places")
}

func loadPlaces(){
let placesData = NSUserDefaults.standardUserDefaults().objectForKey("places") as? NSData

if let placesData = placesData {
let placesArray = NSKeyedUnarchiver.unarchiveObjectWithData(placesData) as? [Place]

if let placesArray = placesArray {
// do something…
}

}
}

How to unarchive data in swift 3?

You can use

 NSKeyedUnarchiver.unarchiveObject(with: Data)

You have Used NSKeyedArchiver . You cannot use this to unarchive an Object.

Saving custom Swift class with NSCoding to UserDefaults

As @dan-beaulieu suggested I answer my own question:

Here is the working code now:

Note: Demangling of the class name was not necessary for the code to work in Playgrounds.

import Foundation

class Blog : NSObject, NSCoding {

var blogName: String?

override init() {}

required init(coder aDecoder: NSCoder) {
if let blogName = aDecoder.decodeObjectForKey("blogName") as? String {
self.blogName = blogName
}
}

func encodeWithCoder(aCoder: NSCoder) {
if let blogName = self.blogName {
aCoder.encodeObject(blogName, forKey: "blogName")
}
}

}

let ud = NSUserDefaults.standardUserDefaults()

var blog = Blog()
blog.blogName = "My Blog"

ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")

if let data = ud.objectForKey("blog") as? NSData {
let unarc = NSKeyedUnarchiver(forReadingWithData: data)
let newBlog = unarc.decodeObjectForKey("root") as Blog
}


Related Topics



Leave a reply



Submit