Ronak Shah
Ronak Shah

Reputation: 1058

How to store a custom array of objects using NSUserDefaults Swift

I am a novice programmer who has started to learn Swift to make apps.

So my question is: "How can I store an array of my objects using NSUserDefaults". Originally, I was looking at this question posted on stack overflow. However, I don't really understand exactly how they did it. After searching on youtube with really no success, I have no choice but ask a question on stack overflow. (I'm hesitant because people often vote my questions off as I'm a beginner asking stupid questions)

So here goes.

I am making a todo list using swift. I have one main class called Task Manager that contains an array of Task objects.

Task Manager (Functionality)

Task (Functionality)

So now, after looking at several online tutorials, here is what i have. (It doesn't work)

Task Manager: Raw Code

//
//  TaskManager.swift
//  ToDoList
//
//  Created by Ronak Shah on 7/5/15.
//  Copyright (c) 2015 com.ShahIndustries. All rights reserved.
//

import UIKit

var taskMgr : TaskManager = TaskManager()


class TaskManager: NSObject{
    var tasks: [Task]? = [Task]()
    var time = 1 //debug, ignore

    override init(){
        super.init()

        let taskData = NSUserDefaults.standardUserDefaults().objectForKey("tasks") as? NSData

        if let taskData = taskData {
            let taskArray = NSKeyedUnarchiver.unarchiveObjectWithData(taskData) as? [Task]

            if let taskArray = taskArray {
                tasks = taskArray
            }

        }
    }

    func addTask(taskName : String, taskDescription : String){
        var newTask = Task(taskName: taskName , taskDescription: taskDescription)

        tasks!.append(newTask)

    }

    func getTaskAtIndex(index: Int) ->Task {
        return tasks![index]
    }


    func saveData() {
        let ud = NSUserDefaults.standardUserDefaults()
        ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(taskMgr), forKey: "tasks")
    }

    required init(coder aDecoder: NSCoder) {
        if let dataTasks = aDecoder.decodeObjectForKey("tasks") as? [Task] {
            self.tasks = dataTasks
        }

    }

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

    func loadData() {
        let ud = NSUserDefaults.standardUserDefaults()
        if let data = ud.objectForKey("tasks") as? NSData {
            //not sure how to get the data
        }
    }
}

Task: Raw Code

//
//  Task.swift
//  ToDoList
//
//  Created by Ronak Shah on 7/5/15.
//  Copyright (c) 2015 com.ShahIndustries. All rights reserved.
//

import Foundation

class Task :NSObject{
    var name : String?
    var desc : String?

    init (taskName : String, taskDescription : String){
        self.name = taskName
        self.desc = taskDescription
    }
    required init(coder aDecoder: NSCoder) {
        if let dataDesc = aDecoder.decodeObjectForKey("desc") as? String {
            self.desc = dataDesc
        }

        if let dataTitle = aDecoder.decodeObjectForKey("name") as? String {
            self.name = dataTitle
        }
    }

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

        if let desc = self.desc {
            aCoder.encodeObject(desc, forKey: "desc")
        }
    }
}

So how might I store my array of tasks (named tasks in Task Manager)?

Upvotes: 1

Views: 1056

Answers (1)

wczekalski
wczekalski

Reputation: 745

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.

Upvotes: 2

Related Questions