Не сохраняются данные в TavleView

swift4
tableview

#1

не могу понять, почему не сохраняются данные? Есть два TableViewCotrollers. Первый динамический, в нем список элементов и pickerView для выбора группы элементов (класс - студенты). Второй TVC статичный, в нем добавляется или редактируется выбранный элемент из предыдущей таблицы. Когда возвращаюсь назад, в первую таблицу, там изменения и добавления видны. Но они не сохраняются на диск и когда приложение запускается заново, они пропадают. Более того, они пропадают даже когда подвигать колесико пикервью. Достаточно выбрать другую позицию, а потом вернуться на прежнюю, и новых данных уже нет. Пересмотрел весь код, наставил везде где только можно функцию сохранения в файл на диск. Не могу понять, где ошибка.

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

#2

а ты уверен в своем классе ClassUnit.saveToFile(classes: classesObject)


#3

как джуниор, сам понимаешь я ни в чем не уверен. Ниже код модели.

Что характерно, в соседних двух табл вью контролерах все работает нормально. Здесь что-то с пикером. Именно когда меняются его значения, то введенные данные пропадают. Даже бывает так, ввожу новую запись, перехожу в список, она есть. Перехожу на другие закладки, возвращаюсь - она есть. Но как только сдвину колесико пикера, запись пропадает.

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<ClassUnit>.self, from: retrivedClassesData) {
            classes = decodeClasses
        }
        return classes
    }
}

#4

и еще в unwind выдает ошибку в предпоследней строке, ниже она закоментирована. Ошибка связана с делегатом, потому как выкидывает именно в файл делегата. Сообщение в консоли - terminating with uncaught exception of type NSException. Выбрасывает именно в момент нажатия на кнопку Save, которая запускает segue на unwind. Может это что-нибудь подскажет.

} else {
            let newIndexPath = IndexPath(row: studentsSelected.count, section: 0)
            studentsSelected.append(student)
            //tableView.insertRows(at: [newIndexPath], with: .automatic)
            ClassUnit.saveToFile(classes: classesObject)
        }

#5

Оп, попробуй просто в методе делегата пикера, который отвечает да изменение didSelected может какой, добавить сохранение


#6

а можно подробнее, вот ниже метод дидСелест пикера, что там нужно сделать?

  func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        classSelected = classesObject[row].classTitle
        studentsSelected = classesObject[row].classStudents
        ClassUnit.saveToFile(classes: classesObject)
    }

#7

а это что такое?__________


[закрыто] coreData, cloudKit, резервное копирование
#8

Максим, смотри, у тебя конечно код более продвинутый, но я так понимаю по всем предыдущим вопросам твоим, что это чужой код и ты пытаешься в нём разобраться. Отсюда совет: во-первых документация языка здесь на сайте - читай прямо всё - пригодится всегда!
Во-вторых даю свой код сохранения простой структуры - словаря - в файл. Посмотри, там проще, ты должен разобраться и сделать по аналогии:

Есть класс долгов:

class Debt {
    
    var name: String
    var summ: Double
    var date: Date
    var status: String
    var comment: String
    var startDate: Date
    var percent: Double
    var phone: String
    var email: String
    var item1: String
    var item2: String
    var item3: String
    var item4: String
    
    var dictonary : NSDictionary {
        
        let dictonary = NSDictionary(objects: [name, summ, date, status, comment, startDate, percent, phone, email, item1, item2, item3, item4], forKeys: ["name" as NSCopying, "summ" as NSCopying, "date" as NSCopying, "status" as NSCopying, "comment" as NSCopying, "startDate" as NSCopying, "percent" as NSCopying, "phone" as NSCopying, "emal" as NSCopying, "item1" as NSCopying, "item2" as NSCopying, "item3" as NSCopying, "item4" as NSCopying])
        
        return dictonary
    }
    
    init(name: String, summ: Double, date: Date, status: String, comment: String, startDate: Date, percent: Double, phone: String, email: String, item1: String, item2: String, item3: String, item4: String) {
        self.name = name
        self.summ = summ
        self.date = date
        self.status = status
        self.comment = comment
        self.startDate = startDate
        self.percent = percent
        self.phone = phone
        self.email = email
        self.item1 = item1
        self.item2 = item2
        self.item3 = item3
        self.item4 = item4
    }
    
    init(dictonary: NSDictionary) {
        self.name = dictonary.object(forKey: "name") as! String
        self.summ = dictonary.object(forKey: "summ") as! Double
        self.date = dictonary.object(forKey: "date") as! Date
        self.status = dictonary.object(forKey: "status") as! String
        self.comment = dictonary.object(forKey: "comment") as! String
        self.startDate = dictonary.object(forKey: "startDate") as! Date
        self.percent = dictonary.object(forKey: "percent") as! Double
        self.phone = dictonary.object(forKey: "phone") as! String
        self.email = dictonary.object(forKey: "emal") as! String
        self.item1 = dictonary.object(forKey: "item1") as! String
        self.item2 = dictonary.object(forKey: "item2") as! String
        self.item3 = dictonary.object(forKey: "item3") as! String
        self.item4 = dictonary.object(forKey: "item4") as! String
    }
    
}

Здесь, как конечно понятно :wink: , свойства экземпляров класса и вычисляемое свойство var dictonary : NSDictionary с помощью которого преобразуются все свойства экземпляра этого класса в NSDictionary для сохранения в файл. Создан отдельный инициализатор для этого свойства init(dictonary: NSDictionary), чтобы обратно можно было преобразовать из NSDictionary при загрузке из файла.

Вот функция сохранения в файл

func saveData(debtMass: [Debt]) {
    var arrayDebtsMass = NSArray()
    for itemDebtsMass in debtsMass {
        arrayDebtsMass = arrayDebtsMass.adding(itemDebtsMass.dictonary) as NSArray
    }
    arrayDebtsMass.write(toFile: NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]/data.plist, atomically: true)
}

Здесть в функцию передаются массив объектов класса Debt, через цикл разбирается на отдельные объекты и, преобразуясь в NSArray, добавляется в новый массив объектов NSArray. После этого сохраняется в файл arrayDebtsMass.write(toFile: . Путь файла прописан в служебную директорию самого приложения. То что после слеша (/data.plist) - это имя файла сохранения - можешь сделать любое. Всё - успешно сохранили в файл данные.

Загрузка

func loadData()->([Debt]) {
    var debtMass : [Debt] = []
    if let loadArray = NSArray(contentsOfFile: NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]/data.plist) {
        debtsMass = []
        var tempData : Debt!
        for itemArray in loadArray {
            tempData = Debt(dictonary: itemArray as! NSDictionary)
            debtsMass.append(tempData)
        }
    } else {
        debtsMass = []
    }
return debtMass  
}

Здесь мы обращаемя к файлу и преобразуем каждый элесент массива NSDictionary через цикл и специальный инициализатор dictonary класса Debt в объекты класса Debt. И возвращаем массив этих элементов класса Debt - всё, данные загружены из файла.

Если всё внимательно посмотришь, то сможешь переделать под свои задачи.

Здесь конечно неоптимально: например, чтобы не дублировать код адреса файла, можно сделать вычисляемую глобальную переменную например так:

var pathForSaveLibrary : String {
    let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]
    return path
}

var pathForSaveData : String {
    let path = pathForSaveLibrary + "/data.plist"
    return path
}

и потом вместо большой записи использовать pathForSaveData, кроме того, при смене адреса, достаточно будет поменять в одном месте, или добавлять новые фалы сохранения проще. Но я просто сделал изначально всё так для наглядности.

Гуру могут поправить, но для новичка должно быть понятно. Ну и в целом этот простой фал можно писать в cloudKit и не париться с coreData :wink:

З.Ы. ну и самое главное - этот код инициализатора не предполагает добавление потом новых элементов в класс - всё будет падать при загрузке. Я разобрался как можно сделать инициализатор, восприимчивый для любых данных класса (и новых и старых, которых нет), но тут в этом проекте пока не добавлял :wink:


#9

ты прав, я использовал код из учебника. И он нормально работает в соседних двух таблвьюконтролерах. А здесь почему-то нет.

Спасибо за пример, буду разбираться.

P,S, И еще, а может этот глюк быть связан с тем, что я вставил в макете пикер вью методом тыка каким-то образом внутрь табл вью?

image

image

image


#10

Это не глюк, а скорее неправильная логика в коде. Судя по скринам он и должен быть внутри тейблвью…


#11

Давайте делать обдуманные вещи)
Пройдите дебагером, посмотрите что вам возвращает пикерВью, и что происходит при сохранении. Вам нужно научиться понимать логику работы написаного(скопированного) вами кода.


#12

пикер вью нормально возвращает требуемое значение и таблвью показывает нужные записи. Но когда указатель пикер вью перемещается на другую позицию, а затем возвращается обратно, эти предыдущие записи в табл вью исчезают. Как это отследить в дебагере?


#13

Все перепробовал, не идет ни в какую. Думаю, что проблема в том, что у тебя модель более простая, все переменные в классе или строка, или дабл, или Date. У меня двухуровневый массив с пользовательским типом.


#14

Эта функция показывает строчку (row). Я бы не делал здесь сохранения данных как в вашем коде:
ClassUnit.saveToFile(classes: classesObject)

это так, влёт. Случайно зашёл в ветку на пару секунд.

Поясню. Вы считали данные предварительно. Они у Вас в оперативной памяти. Показали эти данные всей прогрессивной общественности и пошли менять или дополнять в другой контроллер. Он записал данные на диск. Вы чешите обратно. И тут эта волшебная функция показывает строчку row и пишет снова старые данные из оперативной памяти на диск.

Мне кажется в этом ключе код просмотреть и вообще читать данные с диска один раз где то. И один раз писать, хотя в этом view вообще как я понял писать не надо ничего – данные же меняются в другом окне.

Приятно всем воскресения! У нас всё получиться! :smiley: