I have a a set of objects in Swift which I am using NSCoding to save to disk so that they can be read into memory the next time the app is run. The item being saved is an array of Volume objects. Each instance of Volume has a volumeNumber Int which is set. Both the Volume and QA class inherit from NSObject and NSCoding.
If the application is run before any data is saved it runs fine, when the encoding function is run my print output confirms that volume numbers appear to be saved appropriately (as 1 and 2 in my example). However when I then reload the application it quits with the error: fatal error: unexpectedly found nil while unwrapping an Optional value and highlights the: let volumeNumber = aDecoder.decodeObject(forKey: "volumeNumber") as! Int
To me it looks like the application is saving the Ints but for some reason is unable to find them when the application tries to read from memory. Can anyone see what I might have done wrong?
This is my Volume class:
import Foundation
class Volume: NSObject, NSCoding {
let volumeNumber: Int
var completed: Bool
var questionsData: [QA]
init (volumeNumber: Int, completed: Bool, questionsData: [QA]) {
self.volumeNumber = volumeNumber
self.completed = completed
self.questionsData = questionsData
}
func percent() -> Int {
var correctAnswersTotal = 0
var percentage = 0
for question in questionsData {
if question.correctAnswer == question.selectedAnswer {
correctAnswersTotal += 1
}
}
percentage = ((correctAnswersTotal / questionsData.count) * 100) as Int
print("percentage is: \(percentage)")
return percentage
}
// MARK: NSCoding
public convenience required init?(coder aDecoder: NSCoder) {
print("Trying to read volumeNumber in decoder now")
let volumeNumber = aDecoder.decodeObject(forKey: "volumeNumber") as! Int
let completed = aDecoder.decodeObject(forKey: "completed") as! Bool
let questionsData = aDecoder.decodeObject(forKey: "questionsData") as! [QA]
self.init(volumeNumber: volumeNumber, completed: completed, questionsData: questionsData)
}
func encode(with aCoder: NSCoder) {
print("about to encode \(volumeNumber) in func encode")
aCoder.encode(volumeNumber, forKey: "volumeNumber")
aCoder.encode(completed, forKey: "completed")
aCoder.encode(questionsData, forKey: "questionsData")
}
}
And this is my view controller which attempts to save/load the data: import UIKit
class VolumeTableViewController: UITableViewController {
static var volumesArray: [Volume] = [Volume(volumeNumber: 1, completed: false, questionsData: [QA(questionsText: "Question 1", answerText: ["Answer A", "Answer B", "Answer C", "Answer D"], correctAnswer: [true, false, false, false], selectedAnswer: [false, false, false, false])])]
override func viewDidLoad() {
print("view did load")
super.viewDidLoad()
if let loadedVolumes = VolumeTableViewController.read() {
print("Using loaded volumes...")
VolumeTableViewController.volumesArray = loadedVolumes
} else {
print("Setting up fresh set of volumes...")
VolumeTableViewController.volumesArray = [Volume(volumeNumber: 1, completed: false, questionsData: [QA(questionsText: "Vol1 Question 1", answerText: ["Answer A", "Answer B", "Answer C", "Answer D"], correctAnswer: [true, false, false, false], selectedAnswer: [false, false, false, false])]),
Volume(volumeNumber: 2, completed: false, questionsData: [QA(questionsText: "Vol 2 Question 1", answerText: ["Answer A", "Answer B", "Answer C", "Answer D"], correctAnswer: [true, false, false, false], selectedAnswer: [false, false, false, false])])]
}
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return VolumeTableViewController.volumesArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "VolumeCell", for: indexPath)
// Configure the cell...
if VolumeTableViewController.volumesArray[indexPath.row].completed {
let percent = VolumeTableViewController.volumesArray[indexPath.row].percent()
cell.textLabel?.text = "Volume \(indexPath.row + 1) \(percent) %"
}
cell.textLabel?.text = "Volume \(indexPath.row + 1)"
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
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToQuestionsTableView" {
if let indexPath = self.tableView.indexPathForSelectedRow {
print("index selected for path for volume is: \(indexPath.row)")
let controller = segue.destination as! QuestionsTableViewController
controller.volume = indexPath.row
}
}
}
// Mark NSCODING
static func save() {
print("About to save...")
let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
let ArchiveURL = DocumentsDirectory.appendingPathComponent("volumesData")
NSKeyedArchiver.archiveRootObject(VolumeTableViewController.volumesArray, toFile: ArchiveURL.path)
}
static func read() -> [Volume]? {
print("About to read()")
let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
let ArchiveURL = DocumentsDirectory.appendingPathComponent("volumesData")
return NSKeyedUnarchiver.unarchiveObject(withFile: ArchiveURL.path) as? [Volume]
}
}