Reputation: 43
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
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.
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
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
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