4

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?

6
  • 1
    And where is the crash of the app occurs? Commented Jan 29, 2018 at 14:05
  • When I try to add new note (it's a note-taking app) pressing the (+) button. No entries have been saved yet so there is nothing to read from. Commented Jan 29, 2018 at 14:08
  • Not familiar with swift, but I don't see any way to overwrite the variables in Sheet? Neither anything in Sheet that implies you can save rows of data in them with the used methods that can be called, or that Sheet inherits these somehow? Commented Jan 29, 2018 at 14:10
  • @Francis print error from log Commented Jan 29, 2018 at 14:12
  • I'm not sure also I'm a student just started to learn Swift. Commented Jan 29, 2018 at 14:13

3 Answers 3

7

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)
        }
    }
}
Sign up to request clarification or add additional context in comments.

8 Comments

Your code seems to work but now changes made seems won't save
UserDefaults.standard.set(try! PropertyListEncoder().encode(notes), forKey: "notes") UserDefaults.standard.synchronize() There are the lines saves the array in UserDefaults. Use it wherever you want.
Which one should I follow? The one above with get and addSheets or the link your provided?
@Francis Are you saving the new note in NoteContentVC?
|
3

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]

1 Comment

Although this is a correct and working (Swift 2) solution in Swift 4 Codable is preferable if the property types are property list compliant.
1

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

1 Comment

Still getting this error: [User Defaults] Attempt to set a non-property-list object ( "Sheet.Sheet" ) as an NSUserDefaults/CFPreferences value for key notes 2018-01-29 22:18:47.567973+0800 Sheet[1706:10257898] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object ( "Sheet.Sheet" ) for key notes'

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.