Encode/Decoding Date with NSCoder in Swift 3?
<p>I'm writing a planner app, based on Apple's FoodTracker tutorial, that stores several strings, a Date, and a UIImage per Assignment. I'm encountering problems encoding/decoding the Date. I don't know for sure which it is because the console outputs change with every slight modification, but from what I can tell, my code saves the Date as nil, and then when it tries to load that Date, it unexpectedly finds nil and crashes. Because I'm relatively new to Swift and Swift 3 is a headache and a half, I have very little idea where the problem really is. Here's the code that I <em>think</em> should work:</p>

<pre><code>class Assignment: NSObject, NSCoding {

//MARK: Properties

var name: String
var className: String
var assignmentDescription: String
var materials: String
var dueDate: Date?
var assignmentImage: UIImage?

//MARK: Archiving Paths

static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("assignments")

//MARK: Types

struct PropertyKey {
    static let nameKey = "name"
    static let classNameKey = "className"
    static let assignmentDescriptionKey = "assignmentDescription"
    static let materialsKey = "materials"
    static let dueDateKey = "dueDate"
    static let assignmentImageKey = "assignmentImage"
}

//MARK: Initialization

init?(name: String, className: String, assignmentDescription: String, materials: String, dueDate: Date, assignmentImage: UIImage?) {
    //Initialize stored properties.
    self.name = name
    self.className = className
    self.assignmentDescription = assignmentDescription
    self.materials = materials
    self.dueDate = dueDate
    self.assignmentImage = assignmentImage

    super.init()

    //Initialization should fail if there is no name and no class.
    if name.isEmpty &amp;&amp; className.isEmpty {
        print("Failed to initialize an assignment.")
        return nil
    }
}

//MARK: NSCoding

func encode(with aCoder: NSCoder) {
    aCoder.encode(name, forKey: PropertyKey.nameKey)
    aCoder.encode(className, forKey: PropertyKey.classNameKey)
    aCoder.encode(assignmentDescription, forKey: PropertyKey.assignmentDescriptionKey)
    aCoder.encode(materials, forKey: PropertyKey.materialsKey)
    aCoder.encode(dueDate, forKey: PropertyKey.dueDateKey)
    aCoder.encode(assignmentImage, forKey: PropertyKey.dueDateKey)
}

required convenience init?(coder aDecoder: NSCoder) {
    //Required fields.
    let name = aDecoder.decodeObject(forKey: PropertyKey.nameKey) as! String
    let className = aDecoder.decodeObject(forKey: PropertyKey.classNameKey) as! String

    //Optional fields.
    let assignmentDescription = aDecoder.decodeObject(forKey: PropertyKey.assignmentDescriptionKey) as? String
    let materials = aDecoder.decodeObject(forKey: PropertyKey.materialsKey) as? String
    let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
    let assignmentImage = aDecoder.decodeObject(forKey: PropertyKey.assignmentImageKey) as? UIImage

    //Must call designated initializer.
    self.init(name: name, className: className, assignmentDescription: assignmentDescription!, materials: materials!, dueDate: dueDate, assignmentImage: assignmentImage)
}
</code></pre>

<p>Any insight at all would be appreciated.</p>

<p><strong>Edit:</strong></p>

<p>With Duncan C's help and Xcode's fix-its, this is what <code>required convenience init?(coder aDecoder: NSCoder)</code> looks like now:</p>

<pre><code>//Required fields.
    let newName = aDecoder.decodeObject(forKey: PropertyKey.nameKey) as! String
    let newClassName = aDecoder.decodeObject(forKey: PropertyKey.classNameKey) as! String

    //Optional fields.
    var newAssignmentImage: UIImage?
    var newDueDate: Date

    let newAssignmentDescription = aDecoder.decodeObject(forKey: PropertyKey.assignmentDescriptionKey) as! String
    let newMaterials = aDecoder.decodeObject(forKey: PropertyKey.materialsKey) as! String

    if aDecoder.containsValue(forKey: PropertyKey.dueDateKey) {
        newDueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
    } else {
        newDueDate = Date()
        if aDecoder.containsValue(forKey: PropertyKey.assignmentImageKey) {
            newAssignmentImage = aDecoder.decodeObject(forKey: PropertyKey.assignmentImageKey) as? UIImage
        } else {
            newAssignmentImage = UIImage(named: "sampleAssignmentImage")
        }
    }

    //Must call designated initializer.
    self.init(name: newName, className: newClassName, assignmentDescription: newAssignmentDescription, materials: newMaterials, dueDate: newDueDate, assignmentImage: newAssignmentImage)!
</code></pre>

<p>It compiles, but it still throws <code>fatal error: unexpectedly found nil while unwrapping an Optional value</code> on the line <code>newDueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date</code></p>

<p><strong>Edit 2:</strong></p>

<p>After going back to my original code and changing <code>?</code> to <code>!</code>, this is what my code looks like: </p>

<pre><code> //Required fields.
    let name = aDecoder.decodeObject(forKey: PropertyKey.nameKey) as! String
    let className = aDecoder.decodeObject(forKey: PropertyKey.classNameKey) as! String

    //Optional fields.
    let assignmentDescription = aDecoder.decodeObject(forKey: PropertyKey.assignmentDescriptionKey) as! String
    let materials = aDecoder.decodeObject(forKey: PropertyKey.materialsKey) as! String
    let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
    let assignmentImage = aDecoder.decodeObject(forKey: PropertyKey.assignmentImageKey) as! UIImage

    //Must call designated initializer.
    self.init(name: name, className: className, assignmentDescription: assignmentDescription, materials: materials, dueDate: dueDate, assignmentImage: assignmentImage)
</code></pre>

<p>It compiles, but it still throws <code>fatal error: unexpectedly found nil while unwrapping an Optional value</code> on the line <code>let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date</code></p>

<p><strong>Edit 3 (Working):</strong></p>

<p>For anyone interested, here are the sections of the original code that were modified to make it work:</p>

<p>Properties:</p>

<pre><code>//You can't encode optional values, so you don't use `?` here.
var dueDate: Date
var assignmentImage: UIImage
</code></pre>

<p>Initializer:</p>

<pre><code>//Removed the `?` after UIImage because it isn't an Optional.
init?(name: String, className: String, assignmentDescription: String, materials: String, dueDate: Date, assignmentImage: UIImage)
</code></pre>

<p>Decoder (<code>required convenience init?(coder aDecoder: NSCoder)</code>):</p>

<pre><code>//Required fields.
let name = aDecoder.decodeObject(forKey: PropertyKey.nameKey) as! String
let className = aDecoder.decodeObject(forKey: PropertyKey.classNameKey) as! String

//Optional fields.
let assignmentDescription = aDecoder.decodeObject(forKey: PropertyKey.assignmentDescriptionKey) as! String //This String should use `!` instead of `?`.
let materials = aDecoder.decodeObject(forKey: PropertyKey.materialsKey) as! String //This String should use `!` instead of `?`.
let dueDate = aDecoder.decodeObject(forKey: PropertyKey.dueDateKey) as! Date
//Check for the UIImage being nil. If it is, assign some default image to it so that it doesn't unwrap nil and crash.
var assignmentImage: UIImage!
if aDecoder.decodeObject(forKey: PropertyKey.assignmentImageKey) == nil {
    assignmentImage = UIImage(named: "SampleAssignmentImage")
}
else {
    assignmentImage = aDecoder.decodeObject(forKey: PropertyKey.assignmentImageKey) as! UIImage
}

//Must call designated initializer.
//`materials` and `assignmentDescription` don't need `!` now because they're already unwrapped.
self.init(name: name, className: className, assignmentDescription: assignmentDescription, materials: materials, dueDate: dueDate, assignmentImage: assignmentImage) 
</code></pre>

<p>It isn't perfect, but it works.</p>