How to Use Nsuserdefaults to Store an Array of Custom Classes in Swift

How to use NSUserDefaults to store an array of custom classes in Swift?

Your Person class should look like this:

Swift 3:

class Person : NSObject, NSCoding{

// Person dictionary variable
var name: String?
var age: String?
var html_url: String?

init(json: NSDictionary) { // Dictionary object
self.name = json["name"] as? String
self.age = json["age"] as? String
self.html_url = json["html_url"] as? String // Location of the JSON file
}

required init?(coder aDecoder: NSCoder) {

self.name = aDecoder.decodeObjectForKey("name") as? String;
self.age = aDecoder.decodeObjectForKey("age") as? String;
self.html_url = aDecoder.decodeObjectForKey("html") as? String;
}

func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.name, forKey: "name");
aCoder.encodeObject(self.age, forKey: "age");
aCoder.encodeObject(self.html_url, forKey: "html");
}
}

And here you have an example of saving and retrieving the array from NSUserDefaults:

let p = Person()
p.name = "person1"
p.age = "12"
p.html_url = "www.google.ro"

let p2 = Person()
p2.name = "person2"
p2.age = "11"
p2.html_url = "www.google.ro"

let array = [p, p2]

let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setValue(NSKeyedArchiver.archivedDataWithRootObject(array), forKey: "persons")
userDefaults.synchronize()

let array : [Person]
array = NSKeyedUnarchiver.unarchiveObjectWithData(userDefaults.objectForKey("persons") as! NSData) as! [Person]
print("\(array[0].name)\(array[1].name)")

Swift 4:

class Person : NSObject, NSCoding{

// Person dictionary variable
var name: String?
var age: String?
var html_url: String?

init(json: NSDictionary) { // Dictionary object
self.name = json["name"] as? String
self.age = json["age"] as? String
self.html_url = json["html_url"] as? String // Location of the JSON file
}

required init?(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObject(forKey: "name") as? String;
self.age = aDecoder.decodeObject(forKey: "age") as? String;
self.html_url = aDecoder.decodeObject(forKey: "html") as? String;
}

func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name");
aCoder.encode(self.age, forKey: "age");
aCoder.encode(self.html_url, forKey: "html");
}
}

How to store array list of custom objects to NSUserDefaults?

You need to implement protocol NSCoding for your custom class. Please see this Swift example.

Save Custom Class Array With NSUserDefaults

Here is the simple example for you to save and retrive class from NSUserDefaults:

    //your class
class Person: NSObject, NSCoding {
var name: String!
var age: Int!
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String
self.age = decoder.decodeObjectForKey("age") as! Int
}
convenience init(name: String, age: Int) {
self.init()
self.name = name
self.age = age
}
func encodeWithCoder(coder: NSCoder) {
if let name = name { coder.encodeObject(name, forKey: "name") }
if let age = age { coder.encodeObject(age, forKey: "age") }

}

}

//store values in class and store it to NSUserDefaults
var newPerson = [Person]()
newPerson.append(Person(name: "Leo", age: 45))
newPerson.append(Person(name: "Dharmesh", age: 25))
let personData = NSKeyedArchiver.archivedDataWithRootObject(newPerson)
NSUserDefaults().setObject(personData, forKey: "personData")

//retrive your values
if let loadedData = NSUserDefaults().dataForKey("personData") {
loadedData
if let loadedPerson = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData) as? [Person] {
loadedPerson[0].name //"Leo"
loadedPerson[0].age //45
}
}

Tested with playground.

Hope this example will help.

Storing array of custom objects in UserDefaults

If you really want to persist your data using UserDefaults the easiest way would be to use a class and conform it to NSCoding. Regarding your global var domainSchemas I would recommend using a singleton or extend UserDefaults and create a computed property for it there:



class DomainSchema: NSObject, NSCoding {
var domain: String
var schema: String
init(domain: String, schema: String) {
self.domain = domain
self.schema = schema
}
required init(coder decoder: NSCoder) {
self.domain = decoder.decodeObject(forKey: "domain") as? String ?? ""
self.schema = decoder.decodeObject(forKey: "schema") as? String ?? ""
}
func encode(with coder: NSCoder) {
coder.encode(domain, forKey: "domain")
coder.encode(schema, forKey: "schema")
}
}


extension UserDefaults {
var domainSchemas: [DomainSchema] {
get {
guard let data = UserDefaults.standard.data(forKey: "domainSchemas") else { return [] }
return (try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)) as? [DomainSchema] ?? []
}
set {
UserDefaults.standard.set(try? NSKeyedArchiver.archivedData(withRootObject: newValue, requiringSecureCoding: false), forKey: "domainSchemas")
}
}
}

Usage:

UserDefaults.standard.domainSchemas = [.init(domain: "a", schema: "b"), .init(domain: "c", schema: "d")]

UserDefaults.standard.domainSchemas // [{NSObject, domain "a", schema "b"}, {NSObject, domain "c", schema "d"}]



If you prefer the Codable approach persisting the Data using UserDefaults as well:



struct DomainSchema: Codable {
var domain: String
var schema: String
init(domain: String, schema: String) {
self.domain = domain
self.schema = schema
}
}


extension UserDefaults {
var domainSchemas: [DomainSchema] {
get {
guard let data = UserDefaults.standard.data(forKey: "domainSchemas") else { return [] }
return (try? PropertyListDecoder().decode([DomainSchema].self, from: data)) ?? []
}
set {
UserDefaults.standard.set(try? PropertyListEncoder().encode(newValue), forKey: "domainSchemas")
}
}
}

Usage:

UserDefaults.standard.domainSchemas = [.init(domain: "a", schema: "b"), .init(domain: "c", schema: "d")]

UserDefaults.standard.domainSchemas // [{domain "a", schema "b"}, {domain "c", schema "d"}]

I think the best option would be to do not use UserDefaults, create a singleton "shared instance", declare a domainSchemas property there and save your json Data inside a subdirectory of you application support directory:

extension URL {
static var domainSchemas: URL {
let applicationSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
let bundleID = Bundle.main.bundleIdentifier ?? "company name"
let subDirectory = applicationSupport.appendingPathComponent(bundleID, isDirectory: true)
try? FileManager.default.createDirectory(at: subDirectory, withIntermediateDirectories: true, attributes: nil)
return subDirectory.appendingPathComponent("domainSchemas.json")
}
}


class Shared {
static let instance = Shared()
private init() { }
var domainSchemas: [DomainSchema] {
get {
guard let data = try? Data(contentsOf: .domainSchemas) else { return [] }
return (try? JSONDecoder().decode([DomainSchema].self, from: data)) ?? []
}
set {
try? JSONEncoder().encode(newValue).write(to: .domainSchemas)
}
}
}

Usage:

Shared.instance.domainSchemas = [.init(domain: "a", schema: "b"), .init(domain: "c", schema: "d")]

Shared.instance.domainSchemas // [{domain "a", schema "b"}, {domain "c", schema "d"}]

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 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)
}
}

How to store a custom array of objects using NSUserDefaults Swift

The docs say:

For NSArray and NSDictionary objects, their contents must be property list objects

There's NSData among the property list types. In order to convert you custom object to NSData you have to serialize it.

You are trying to implement the archiving step on Task and TaskManager objects which adds complexity. The easiest way to achieve your goal is to archive (convert to NSData) just Task objects and add them to an array before saving to the defaults. In order to do that you have to convert the tasks array of type [Task] to an array of type [NSData] It would look like this.

func saveData() {
let tasksAsData = tasks.map { task in
return NSKeyedArchiver.archivedDataWithRootObject(task)
}
let ud = NSUserDefaults.standardUserDefaults()
ud.setObject(tasksAsData, forKey: "tasks")
}

When you get the data back you have to convert NSData objects into Tasks. You can do it in the following way.

func loadData() {
let ud = NSUserDefaults.standardUserDefaults()
if let data = ud.objectForKey("tasks") as? [NSData] {
tasks = data.map { data in
return NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
}
}

There you go! I think you can take it from here.


As a side note I am advising not to use NSUserDefaults for such purpose. The defaults are meant as a simple way of saving little pieces of (usually) unstructured data. When you add more properties to your tasks it is likely to get cumbersome (you'll probably serialize things like image, date, or location). It is not space efficient nor is it fast. I advise to take a look at other ways to persist data on iOS like SQLite, Core Data, Realm or many others.

Saving Array of Custom Object

You are trying to save an array of custom objects to UserDefaults. Your custom object isn't a property list object You should use Codable to save non-property list object in UserDefaults like this.

Swift 4

Custom Class

class Sheet: Codable {
var title = ""
var content = ""
}

ViewController.swift

class ViewController: UIViewController {

var notes = [Sheet]()

override func viewDidLoad() {
super.viewDidLoad()

getSheets()
addSheets()
getSheets()
}
func getSheets()
{
if let storedObject: Data = UserDefaults.standard.data(forKey: "notes")
{
do
{
notes = try PropertyListDecoder().decode([Sheet].self, from: storedObject)
for note in notes
{
print(note.title)
print(note.content)
}
}
catch
{
print(error.localizedDescription)
}
}
}
func addSheets()
{
let sheet1 = Sheet()
sheet1.title = "title1"
sheet1.content = "content1"

let sheet2 = Sheet()
sheet2.title = "title1"
sheet2.content = "content1"

notes = [sheet1,sheet2]
do
{
UserDefaults.standard.set(try PropertyListEncoder().encode(notes), forKey: "notes")
UserDefaults.standard.synchronize()
}
catch
{
print(error.localizedDescription)
}
}
}

store array of custom class with nested custom class to standardUserDefaults

it's very easy.

write:

self.something = aDecoder.decodeObjectForKey("something") as! Class2

read:

aCoder.encodeObject(something, forKey: "something")


Related Topics



Leave a reply



Submit