Search code examples
iosarraysswiftnsuserdefaultssaving-data

Saving array using NSUserDefaults crashes app


I feel as if I am doing things correctly, but I am getting an error at the end of my data conversion and retrieval. Please see the code below:

class Task:NSObject, NSCoding {
        var name:String
        var notes:String
        var date:NSDate
        var taskCompleted:Bool

        init(name:String, notes:String,date:NSDate, taskCompleted:Bool){
            self.name = name
            self.notes = notes
            self.date = date
            self.taskCompleted = taskCompleted
        }

        required init(coder decoder: NSCoder){
            self.name = (decoder.decodeObjectForKey("name") as! String?)!
            self.notes = (decoder.decodeObjectForKey("notes") as! String?)!
            self.date = (decoder.decodeObjectForKey("date") as! NSDate?)!
            self.taskCompleted = (decoder.decodeObjectForKey("taskCompleted") as! Bool?)!
        }

        func encodeWithCoder(coder: NSCoder) {
            coder.encodeObject(self.name, forKey: "name")
            coder.encodeObject(self.notes, forKey: "notes")
            coder.encodeObject(self.date, forKey: "date")
            coder.encodeObject(self.taskCompleted, forKey: "taskCompleted")
        }
    }

I then save and get the data as follows:

let nowData = NSKeyedArchiver.archivedDataWithRootObject([nowTasks])
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(nowData, forKey: "nowData")

let loadedData = defaults.dataForKey("nowData")
let loadedArray = NSKeyedUnarchiver.unarchiveObjectWithData(loadedData!) as! [Task]

When I call print(loadedArray.first) I get the error: NSArray element failed to match the Swift Array Element type


Solution

  • Looks like your code should work fine even with some really weird forced casting going on in your decoder method. Try like this:

    class Task: NSObject, NSCoding {
        var name = String()
        var notes = String()
        var date: NSDate
        var taskCompleted: Bool
        init(name: String, notes: String, date: NSDate, taskCompleted: Bool){
            self.name = name
            self.notes = notes
            self.date = date
            self.taskCompleted = taskCompleted
        }
        required init(coder decoder: NSCoder){
            self.name = decoder.decodeObjectForKey("name") as! String
            self.notes = decoder.decodeObjectForKey("notes") as! String
            self.date = decoder.decodeObjectForKey("date") as! NSDate
            self.taskCompleted = decoder.decodeBoolForKey("taskCompleted")
        }
        func encodeWithCoder(coder: NSCoder) {
            coder.encodeObject(name, forKey: "name")
            coder.encodeObject(notes, forKey: "notes")
            coder.encodeObject(date, forKey: "date")
            coder.encodeBool(taskCompleted, forKey: "taskCompleted")
        }
    }
    

    Testing with plist files:

    let task1 = Task(name: "task1", notes: "note a", date: NSDate(), taskCompleted: false)
    let task2 = Task(name: "task2", notes: "note b", date: NSDate(), taskCompleted: true)
    
    let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
    let fileURL = documentsDirectory.URLByAppendingPathComponent("data.plist")
    
    if let filePath = fileURL.path {
        NSKeyedArchiver.archiveRootObject([task1,task2], toFile: filePath)
        if let loadedArray = NSKeyedUnarchiver.unarchiveObjectWithFile(filePath) as? [Task] {
            print(loadedArray.count)
    
            print(loadedArray.first?.name ?? "")
            print(loadedArray.first?.notes ?? "")
            print(loadedArray.first!.date )
            print(loadedArray.first!.taskCompleted)
    
            print(loadedArray.last?.name ?? "")
            print(loadedArray.last?.notes ?? "")
            print(loadedArray.last!.date )
            print(loadedArray.last!.taskCompleted)
        }
    }