Francis
Francis

Reputation: 43

Saving Array of Custom Object

I am try to save and retrieve notes data with custom object called Sheet. But I am having crashes when it runs. Is this the correct way to do it or is there any other ways to solve this?

The Sheet Class

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

Here is the class for UITableViewController

class NotesListTableVC: UITableViewController {

    var notes = [Sheet]()

    override func viewDidLoad() {
        super.viewDidLoad()

        if let newNotes = UserDefaults.standard.object(forKey: "notes") as? [Sheet] {
            //set the instance variable to the newNotes variable
            notes = newNotes
        }
    }


    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // Return the number of rows in the section.
        return notes.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCell(withIdentifier: "notesCELL", for: indexPath)

        cell.textLabel!.text = notes[indexPath.row].title

        return cell
    }

    // Add new note or opening existing note
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "editNote" {
            var noteContentVC = segue.destination as! NoteContentVC
            var selectedIndexPath = tableView.indexPathForSelectedRow
            noteContentVC.note = notes[selectedIndexPath!.row]
        }
        else if segue.identifier == "newNote" {
            var newEntry = Sheet()
            notes.append(newEntry)
            var noteContentVC = segue.destination as! NoteContentVC
            noteContentVC.note = newEntry
        }
        saveNotesArray()
    }

    // Reload the table view
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.tableView.reloadData()
    }

    // Deleting notes
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        notes.remove(at: indexPath.row)
        tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
    }

    // Save the notes
    func saveNotesArray() {
        // Save the newly updated array
        UserDefaults.standard.set(notes, forKey: "notes")
        UserDefaults.standard.synchronize()
    }

}

And where should I call the saveNotesArray function?

Upvotes: 4

Views: 2403

Answers (3)

RajeshKumar R
RajeshKumar R

Reputation: 15788

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

Upvotes: 7

Clegrand
Clegrand

Reputation: 82

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 Array, Dictionary, String, Data, Number, and Date in UserDefaults.

You need to convert the object to Data (like you have in some of the code) and store that Data in UserDefaults. You can even store an Array of Data if you need to.

When you read back the array you need to unarchive the Data to get back your Sheet objects.

Change your Sheet object to :

class Sheet: NSObject, NSCoding {
    var title: String
    var content: String


    init(title: String, content: String) {
        self.title = title
        self.content = content
    }

    required convenience init(coder aDecoder: NSCoder) {
        let title = aDecoder.decodeObject(forKey: "title") as! String
        let content = aDecoder.decodeObject(forKey: "content") as! String
        self.init(title: title, content: content)
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(title, forKey: "title")
        aCoder.encode(content, forKey: "content")
    }
}

into a function like :

func loadData() {
    if let decoded  = userDefaults.object(forKey: "notes") as? Data, let notes = NSKeyedUnarchiver.unarchiveObject(with: decoded) as? [Sheet] {
         self.notes = notes
         self.tableView.reloadData()
    }

}

and then call :

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    self.loadData()
}

saveNotesArray can be called after new Notes added with :

func saveNotesArray() {
    // Save the newly updated array
    var userDefaults = UserDefaults.standard
    let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: notes)
    userDefaults.set(encodedData, forKey: "notes")
    userDefaults.synchronize()
}

Upvotes: 1

Oleg Gordiichuk
Oleg Gordiichuk

Reputation: 15512

You give answer to the question that you ask.

App crash log.

   [User Defaults] Attempt to set a non-property-list object ( "Sheet.Sheet" )

Official Apple info.

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. For more details, see Preferences and Settings Programming Guide.

One of the possible solution:

class Sheet : NSObject, NSCoding{
    var title:String?
    var content:String?

    func encode(with aCoder: NSCoder) {
        aCoder.encodeObject(self.title, forKey: "title")
        aCoder.encodeObject(self.content, forKey: "content")
    }

    required init?(coder aDecoder: NSCoder) {
        self.title = aDecoder.decodeObject(forKey: "title") as? String
        self.content = aDecoder.decodeObject(forKey: "content") as? String
    }
}

Save

userDefaults.setValue(NSKeyedArchiver.archivedDataWithRootObject(sheets), forKey: "sheets")

Load

sheets = NSKeyedUnarchiver.unarchiveObjectWithData(userDefaults.objectForKey("sheets") as! NSData) as! [Sheet]

Upvotes: 3

Related Questions