Why doesn't the data save in TableView?

I'm a beginner, please help me to understand why it happens?


I have two TableViewCotrollers - dynamic and static. When I add or edit item in static TVC it pass to the dynamis TVC. This data is visible in the table. But after that it is not saved. When i open the app again these changes are missing and if I roll the wheel of the pickerView these changes disappear.


I put the function of saving to file on disk wherever possible, but it still does not work. What is my mistake?



TVC 1 - Dynamic


import UIKit

class StudentsTableViewController: UITableViewController, UIPickerViewDataSource, UIPickerViewDelegate {
    
    @IBOutlet weak var pickerViewStudents: UIPickerView!
    var classesObject = [ClassUnit]()
    var classSelected: String = ""
    var studentsSelected: [String] = [] {
        didSet {
            tableView.reloadData()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.pickerViewStudents.delegate = self
        self.pickerViewStudents.dataSource = self
        
        classesObject = ClassUnit.loadFromFile()
        navigationItem.leftBarButtonItem = editButtonItem
    }

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return classesObject.count
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {        
        classesObject = ClassUnit.loadFromFile()
        return classesObject[row].classTitle
    }
    
    override func viewWillAppear(_ animated: Bool) {
        classesObject = ClassUnit.loadFromFile()
        pickerViewStudents.reloadAllComponents()
        tableView.reloadData()
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        classSelected = classesObject[row].classTitle
        studentsSelected = classesObject[row].classStudents
        ClassUnit.saveToFile(classes: classesObject)
    }

    // MARK: - Table view data source//

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return studentsSelected.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ClassTitleStudents", for: indexPath)
        cell.textLabel?.text = studentsSelected[indexPath.row]
        cell.showsReorderControl = true
        ClassUnit.saveToFile(classes: classesObject)
        return cell
    }

    override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) ->
        UITableViewCellEditingStyle {
            return .delete
    }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            studentsSelected.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
            ClassUnit.saveToFile(classes: classesObject)
        }
    }

    override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
        let moveStudent = studentsSelected.remove(at: fromIndexPath.row)
        studentsSelected.insert(moveStudent, at: to.row)
        tableView.reloadData()
        ClassUnit.saveToFile(classes: classesObject)
    }

    // MARK: - Navigation

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        if segue.identifier == "EditStudentSegue" {
            let indexPath = tableView.indexPathForSelectedRow!
            let student = studentsSelected[indexPath.row]
            let nav = segue.destination as! UINavigationController
            let  editStudentName = nav.topViewController as! AddEditStudentTableViewController
            editStudentName.student = student
            ClassUnit.saveToFile(classes: classesObject)
        }
    }
    
    @IBAction func unwindToStudentsTableView(segue: UIStoryboardSegue) {
        guard segue.identifier == "saveUnwindStudents" else { return }
        let sourceViewController = segue.source as! AddEditStudentTableViewController        
        let student = sourceViewController.student
        if let selectedIndexPath = tableView.indexPathForSelectedRow {
            studentsSelected[selectedIndexPath.row] = student
            tableView.reloadRows(at: [selectedIndexPath], with: .none)
            ClassUnit.saveToFile(classes: classesObject)
        } else {
            let newIndexPath = IndexPath(row: studentsSelected.count, section: 0)
            studentsSelected.append(student)
            //tableView.insertRows(at: [newIndexPath], with: .automatic)
            ClassUnit.saveToFile(classes: classesObject)
        }
    }
}



TVC 2 - Static


import UIKit

class AddEditStudentTableViewController: UITableViewController {
    
    @IBOutlet weak var nameStudent: UITextField!
    
    @IBOutlet weak var saveButton: UIBarButtonItem!
    
    var classesObject = [ClassUnit]()
    
    var student = ""

    override func viewDidLoad() {
        super.viewDidLoad()
        
        nameStudent.text = student
        updateSveButton()
        classesObject = ClassUnit.loadFromFile()
    }

    func updateSveButton() {
        let nameOfStudent = nameStudent.text ?? ""
        saveButton.isEnabled = !nameOfStudent.isEmpty
        ClassUnit.saveToFile(classes: classesObject)
    }
    
    @IBAction func textEditingChanged(_ sender: UITextField) {
        updateSveButton()
    }


    // MARK: - Navigation

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        guard segue.identifier == "saveUnwindStudents" else { return }
        
        let nameOfStudent = nameStudent.text ?? ""
        
        student = nameOfStudent
        
        ClassUnit.saveToFile(classes: classesObject)
    }
}

Replies

can I see your saveToFile() fun to see if everything is ok with that?

Yes, of course. This method works well in two other similar TVC in this app.


import Foundation

class ClassUnit: Codable {
    var classTitle: String
    var classStudents: [String]!
   
    init(classTitle: String, classStudents: [String]) {
        self.classTitle = classTitle
        self.classStudents = classStudents
    }
   
    static let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    static let archiveUrl = documentsDirectory.appendingPathComponent("classes").appendingPathExtension("plist")
   
    static func saveToFile(classes: [ClassUnit]) {
        let propertyListEncoder = PropertyListEncoder()
        let encodeClasses = try? propertyListEncoder.encode(classes)
        try? encodeClasses?.write(to: archiveUrl, options: .noFileProtection)
       
    }
   
    static func loadFromFile() -> [ClassUnit] {
        let propertyListDecoder = PropertyListDecoder()
        var classes = [ClassUnit]()
        if let retrivedClassesData = try? Data(contentsOf: archiveUrl), let decodeClasses = try? propertyListDecoder.decode(Array.self, from: retrivedClassesData) {
            classes = decodeClasses
        }
        return classes
    }
}

try this:


replace saveToFile() with this:

static func saveToFile(classes: [ClassUnit]) {
                    do{
                        let data = try NSKeyedArchiver.archivedData(withRootObject: classes, requiringSecureCoding: false)
                        UserDefaults.standard.set(data, forKey: "ClassesData")
                    }catch {
                        
                    }
                }

and replace loadFromFile() with this:

static func loadFromFile() -> [ClassUnit] {
                    var classes = [ClassUnit]()
                    do {
                        let data =  UserDefaults.standard.data(forKey: "ClassesData")
                        let decodedObject = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [ClassUnit]
                        classes = decodedObject
                    }
                    return classes
                }


Hope it Helps 🙂

You'll definitely not want to save your file in tableView(_:cellForRowAt:). That API is only ever going to pull values from your model object for display in the table (i.e. a read-only operation).


You may also want to consider putting your save in just a single location. For example, in an unwind segue if the user moves off of that screen. If your screen is modal, you could thus save when the user taps on Done/Save to dismiss the modal UI.


In terms of what to use for saving the file, PropertyListEncoder is definitely easier to use as it relies on the Codable protocol. And, most times your model object's properties already honor Codbable, so no extra coding needed. NSKeyedArchiver/Unarchiver (at least with the early versions of Swift) were extremely buggy.


One thing about PropertyListEncoder though is that you're limited on what types you can store with that. NSKeyedArchiver would have a leg up over that, but of course you'd have to inplement init(coder) and encode(coder) functions on your model object.

sorry, I was sick.


Thank you for your help. I really appreciate this.


Today I tried to use this code, but the program gives an error. This line:


let decodedObject = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [ClassUnit]


This message - about (data):


Value of optional type 'Data?' must be unwrapped to a value of type 'Data'


I tried to use exclamation mark - (data!) - but there are new errors appear. How to fix it?

Thank you for the information. It is very useful for me.

Hey, anubody can help with this question?