Проблема: я реализовал сохраняемость данных в своем приложении для iOS с помощью NSKeyedArchiver, и в настоящее время оно сохраняет иерархические данные, но только из класса верхнего уровня. Как я могу сделать возможным сохранение всей иерархии с любого уровня?
Предыстория. Я разрабатываю приложение для iOS, в котором используется структура данных иерархических классов, например. Школа, Класс, Студент. По сути, класс School содержит массив Classrooms (вместе с другими свойствами, такими как район, имя, номер телефона и т. д.), класс Classroom содержит массив Student (вместе с другими свойствами, такими как учитель, номер комнаты и т. д.) и Студенческий класс имеет свойства для каждого учащегося (например, имя, класс, курсы и т. д.).
Приложение имеет три контроллера представления, по одному на каждый уровень иерархии, что позволяет изменять данные на каждом уровне: DistrictTableViewController имеет массив объектов School и может добавлять/удалять элементы массива, SchoolTableViewController имеет массив объектов Classroom и может добавлять /delete элементы из массива объектов Classroom, а ClassroomViewController позволяет пользователю добавлять/удалять/редактировать учащихся.
Я реализовал сохраняемость данных во всех трех классах с помощью NSCoding, и в настоящее время он работает для сохранения данных в иерархии, но я могу сохранить данные только из DistrictTableVC верхнего уровня (точка входа в приложение). DistrictTableVC имеет метод saveSchools(). Вместо этого я хочу иметь возможность сохранять изменения из любого из трех ViewControllers, например. изменение свойства Student немедленно сохранит объект Student, а также массив Student в Classroom и массив Classrooms в School.
Текущая конфигурация такова, что DistrictTableVC передает один объект School в SchoolTableVC, SchoolTableVC передает один объект Classroom в ClassroomVC. Я думаю, что мне следует делать вместо этого:
- создайте новый класс верхнего уровня под названием District, который содержит массив школ, а также использует NSCoding
- передавать объект District между тремя VC вместо отдельных объектов более низкого уровня
- переместите метод saveSchools() из DistrictTableVC в новый класс District, что позволит мне вызывать его из любого из трех ViewController'ов.
Поскольку я не профессионал, я протягиваю руку, чтобы увидеть:
- я на правильном пути? или
- возможно, кто-то знает лучший способ сделать это?
Спасибо, что прочитали!!
class DistrictTableViewController: UITableViewController {
private let reuseIdentifier = "schoolCell"
var schoolsArray = [School]()
override func viewDidLoad() {
super.viewDidLoad()
self.navBarTitle.title = "Schools"
// Load saved Schools if they exist, otherwise load sample data
if let savedSchools = loadSchools() {
schoolsArray += savedSchools
print("Loading saved schools")
// Update all School stats
updateSchoolListStats()
} else {
// Load the sample data
loadSampleSchools()
print("Failed to load saved data. Loading sample data...")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: TableView datasource
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return schoolsArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! SchoolTableViewCell
// Configure the cell...
let school = schoolsArray[indexPath.row]
school.calcSchoolStats()
return cell
}
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
schoolsArray.remove(at: indexPath.row)
saveSchools()
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
// Deselect any selected cells
for (_, cell) in tableView.visibleCells.enumerated() {
cell.isSelected = false
}
// SchoolTableViewCell pressed: pass the selected school to SchoolsTableViewController
if (segue.identifier ?? "") == "showSchoolDetail" {
//guard let schoolsTableViewController = segue.destination as? SchoolsTableViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
guard let selectedSchoolCell = sender as? SchoolTableViewCell else {
fatalError("Unexpected sender: \(String(describing: sender))")
}
guard let indexPath = tableView.indexPath(for: selectedSchoolCell) else {
fatalError("The selected SchoolTableViewCell is not being displayed by the table")
}
schoolTableViewController.school = schoolsArray[indexPath.row]
}
// Add button pressed: show SchoolAttributesViewController
if addBarButtonItem == sender as? UIBarButtonItem {
guard segue.destination is SchoolAttributesViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
}
}
@IBAction func unwindToSessionsTableViewController(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? SchoolsTableViewController, let school = sourceViewController.school {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing session
schoolsArray.array[selectedIndexPath.row] = school
tableView.reloadRows(at: [selectedIndexPath], with: .none)
} else {
// Add a new school to the Table View
schoolsArray.insert(session, at: 0) // Update date source; add new school to the top of the table
let newIndexPath = IndexPath(row: 0, section: 0)
tableView.insertRows(at: [newIndexPath], with: .automatic)
tableView.cellForRow(at: newIndexPath)?.isSelected = true
tableView.cellForRow(at: newIndexPath)?.selectedBackgroundView = bgColorView
}
//updateSessionListStats()
//sessionsTableView.reloadData()
saveSchools()
}
}
//MARK: Actions
private func saveSchools() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(schoolsArray, toFile: School.ArchiveURL.path)
if isSuccessfulSave {
os_log("Schools successfully saved", log: OSLog.default, type: .debug)
} else {
os_log("Failed to save schools...", log: OSLog.default, type: .error)
}
}
//MARK: Private Methods
private func updateSchoolListStats() {
for (_, school) in schoolsArray.array.enumerated() {
for (_, classroom) in school.classroomArray.enumerated() {
classroom.calcStats()
}
school.calcSchoolStats()
}
}
private func loadSchools() -> [School]? {
return NSKeyedUnarchiver.unarchiveObject(withFile: School.ArchiveURL.path) as? [School]
}
class School: NSObject, NSCoding {
//MARK: Properties
var name: String
var district: String
var phoneNumber: Int
var classroomArray = [Classroom]()
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("schoolsArray")
init (name: String = "Default", district: String = "", phoneNumber: Int = -1, classroomArray = [Classroom]()) {
self.name = name
self.district = district
self.phoneNumber = phoneNumber
self.classroomArray = classroomArray
}
func calcSchoolStats() {
}
//MARK: NSCoding Protocol
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(district, forKey: "district")
aCoder.encode(phoneNumber, forKey: "phoneNumber")
aCoder.encode(classroomArray, forKey: "classroomArray")
}
required convenience init?(coder aDecoder: NSCoder) {
// The name is required. If we cannot decode a name string, the initializer should fail.
guard let name = aDecoder.decodeObject(forKey: "name") as? String else {
os_log("Unable to decode the name for a School object.", log: OSLog.default, type: .debug)
return nil
}
let district = aDecoder.decodeObject(forKey: "district") as! String
let phoneNumber = aDecoder.decodeInteger(forKey: "phoneNumber")
let classroomArray = aDecoder.decodeObject(forKey: "classroomArray") as! [Classroom]
// Must call designated initializer.
self.init(name: name, district: district, phoneNumber: phoneNumber, classroomArray: classroomArray)
}
}
class Classroom: NSObject, NSCoding {
//MARK: Properties
var teacher: String
var roomNumber: Int
var studentArray = [Student]()
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("classroomsArray")
init (teacher: String = "", building: Int = -1, studentArray = [Student]()) {
self.teacher = teacher
self.roomNumber = roomNumber
self.studentArray = studentArray
}
func calcStats() {
}
//MARK: NSCoding Protocol
func encode(with aCoder: NSCoder) {
aCoder.encode(teacher, forKey: "teacher")
aCoder.encode(roomNumber, forKey: "roomNumber")
aCoder.encode(studentArray, forKey: "studentArray")
}
required convenience init?(coder aDecoder: NSCoder) {
// The teacher is required. If we cannot decode a teacher string, the initializer should fail.
guard let teacher = aDecoder.decodeObject(forKey: "teacher") as? String else {
os_log("Unable to decode the teacher for a Classroom object.", log: OSLog.default, type: .debug)
return nil
}
let roomNumber = aDecoder.decodeInteger(forKey: "roomNumber")
let studentArray = aDecoder.decodeObject(forKey: "studentArray") as! [Student]
// Must call designated initializer.
self.init(teacher: teacher, roomNumber: roomNumber, studentArray: studentArray)
}
}
class Student: NSObject, NSCoding {
//MARK: Properties
var first: String
var last: String
var grade: Int
var courses: [String]
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("students")
init (first: String = "", last: String = "", grade: Int = -1, courses = [String]()) {
self.first = first
self.last = last
self.grade = grade
self.courses = courses
}
//MARK: NSCoding Protocol
func encode(with aCoder: NSCoder) {
aCoder.encode(first, forKey: "first")
aCoder.encode(last, forKey: "last")
aCoder.encode(grade, forKey: "grade")
aCoder.encode(courses, forKey: "courses")
}
required convenience init?(coder aDecoder: NSCoder) {
// The first name is required. If we cannot decode a first name string, the initializer should fail.
guard let first = aDecoder.decodeObject(forKey: "first") as? String else {
os_log("Unable to decode the first name for a Student object.", log: OSLog.default, type: .debug)
return nil
}
let last = aDecoder.decodeObject(forKey: "last") as! String
let grade = aDecoder.decodeInteger(forKey: "grade")
let courses = aDecoder.decodeObject(forKey: "courses") as! [String]
// Must call designated initializer.
self.init(first: first, last: last, grade: grade, courses: courses)
}
}