Archiving: Document.swift and Employees.swift for Swift 4.2, Xcode 10.1


#1

In the Archiving chapter, these updates of Document.swift and Employees.swift for Swift 4.2, Xcode 10.1 are what worked for me. Hope it helps. cheers.

//
//  Employee.swift
//  RaiseMan
//
//  Created by Nicholas Teissler on 2/20/15.
//  Copyright (c) 2014 Big Nerd Ranch. All rights reserved.
//
//  Updated by Frank on 11/8/18

import Foundation

class Employee: NSObject, NSCoding {
    @objc dynamic var name: String? = "New Employee"
    @objc dynamic var raise: NSNumber? = 0.05
    
    
    // MARK: - NSCoding
    
    func encode(with aCoder: NSCoder) {
        if let name = name { aCoder.encode(name, forKey: "name") }
        if let raise = raise { aCoder.encode(raise, forKey: "raise") }
    }
    
    required init?(coder aDecoder: NSCoder) {
        name = aDecoder.decodeObject(forKey: "name") as! String?
        raise = aDecoder.decodeObject(forKey: "raise") as! NSNumber?
        super.init()
    }
    
    override init() {
        super.init()
    }
    

    
    override func validateValue(_ ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>, forKey inKey: String) throws {
        if raise == nil
        {
            let domain = "UserInputValidationErrorDomain"
            let code = 0
            let userInfo = [NSLocalizedDescriptionKey: "Error, raise is nil"]
            
            throw NSError(domain: domain,
                          code: code,
                          userInfo: userInfo)
        }
    }
}


//
//  Document.swift
//  RaiseMan
//
//  Created by Nicholas Teissler on 2/20/15.
//  Copyright (c) 2014 Big Nerd Ranch. All rights reserved.
//
//  Updated by Frank on 11/8/18

import Cocoa

private var KVOContext: Int = 0

class Document: NSDocument, NSWindowDelegate {
    @IBOutlet weak var tableView: NSTableView!
    @IBOutlet weak var arrayController: NSArrayController!
    
    @objc dynamic var employees: [Employee] = [] {
        willSet {
            for employee in employees {
                stopObservingEmployee(employee: employee)
            }
        }
        didSet {
            for employee in employees {
                startObservingEmployee(employee: employee)
            }
        }
    }
    
    // MARK: - Actions
    
    @IBAction func addEmployee(sender: NSButton) {
        let windowController = windowControllers[0]
        let window = windowController.window!
        
        let endedEditing = window.makeFirstResponder(window)
        if !endedEditing {
            Swift.print("Unable to end editing")
            return
        }
        
        let undo: UndoManager = undoManager!
        
        // Has an edit occurred already in this event?
        if undo.groupingLevel > 0 {
            // Close the last group
            undo.endUndoGrouping()
            // Open a new group
            undo.beginUndoGrouping()
        }
        
        // Create the object
        let employee = arrayController.newObject() as! Employee
        
        // Add it to the array controller's content array
        arrayController.addObject(employee)
        
        // Re-sort (in case the use has sorted a column) 
        arrayController.rearrangeObjects()
        
        // Get the sorted array
        let sortedEmployees = arrayController.arrangedObjects as! [Employee]
        
        // Find the object just added
        let row = sortedEmployees.index(of: employee)!
        
        // Begin the edit in the first column
        Swift.print("starting edit of \(employee) in row \(row)")
        tableView.editColumn(0, row: row, with: nil, select: true)
    }
    
    // MARK: - Accessors
    
    @objc func insertObject(_ employee: Employee, inEmployeesAtIndex index: Int) {
        Swift.print("adding \(employee) to the employees array")    // since method is converted to ObjC, need to use 'Swift.print', instead of 'print'
        
        // Add the inverse of this operation to the undo stack
        let undo: UndoManager = undoManager!
        (undo.prepare(withInvocationTarget: self) as AnyObject).removeObject(fromEmployeesAtIndex: employees.count)
        
        if !undo.isUndoing
        {
            undo.setActionName("Add Person")
        }
        
        employees.append(employee)
    }
    
    
    @objc func removeObject(fromEmployeesAtIndex index: Int) {
        let employee: Employee = employees[index]
        
        Swift.print("removing \(employee) from the employees array")
        
        // Add the inverse of this operation to the undo stack
        let undo: UndoManager = undoManager!
        (undo.prepare(withInvocationTarget:self) as AnyObject).insertObject(employee, inEmployeesAtIndex: index)
        
        if !undo.isUndoing
        {
            undo.setActionName("Remove Person")
        }
        
        // Remove the Employee from the array
        employees.remove(at: index)
    }
    
    // MARK: - Key Value Observing
    
    func startObservingEmployee(employee: Employee) {
        employee.addObserver(self, forKeyPath: "name", options: .old, context: &KVOContext)
        employee.addObserver(self, forKeyPath: "raise", options: .old, context: &KVOContext)
    }
    
    func stopObservingEmployee(employee: Employee) {
        employee.removeObserver(self, forKeyPath: "name", context: &KVOContext)
        employee.removeObserver(self, forKeyPath: "raise", context: &KVOContext)
    }
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context != &KVOContext
        {
            // If the context does not match, this message
            // must be intended for our superclass.
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
            return
        }
        
        var oldValue: AnyObject? = change![NSKeyValueChangeKey.oldKey] as AnyObject
        if oldValue is NSNull
        {
            oldValue = nil
        }
        
        let undo: UndoManager = undoManager!
        Swift.print("oldValue = \(String(describing: oldValue))")
        
        (undo.prepare(withInvocationTarget: object!) as AnyObject).setValue(oldValue, forKeyPath: keyPath!)
    }
    
    
    
    // MARK - Lifecycle
    
    override init() {
        super.init()
        // Add your subclass-specific initialization here.

    }


    override class var autosavesInPlace: Bool
    {
        return true
    }
    
    
    override var windowNibName: NSNib.Name?
    {
        // Returns the nib file name of the document
        // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this property and override -makeWindowControllers instead.
        return NSNib.Name("Document")
    }
    
    // MARK: - NSWindowDelegate
    
    func windowWillClose(_ notification: Notification) {
        employees = []
    }
    
    

    override func data(ofType typeName: String) throws -> Data{
        // Insert code here to write your document to data of the specified type, throwing an error in case of failure.
        // Alternatively, you could remove this method and override fileWrapper(ofType:), write(to:ofType:), or write(to:ofType:for:originalContentsURL:) instead.
        
        // NB: FOR FILE LOADING/SAVING, YOU NEED TO CHANGE APP SANDBOX SETTINGS.
        //     ... Under Project > Target > Capabilities > App Sandbox > File Access > User Selected File > "Read/Write"
        
        // End editing
        tableView.window?.endEditing(for: nil)
        
        // Create an NSData object from the employees array
        guard let archive: Data = try? NSKeyedArchiver.archivedData(withRootObject: employees, requiringSecureCoding: false)
        else
        {
            let outError:NSError! = NSError(domain: "Migrator", code: 0, userInfo: [NSLocalizedDescriptionKey : "The file could not be written"])
            throw outError
        }
    
    return archive
    }
    
    override func read(from data: Data, ofType typeName: String) throws {
        // Insert code here to read your document from the given data of the specified type, throwing an error in case of failure.
        // Alternatively, you could remove this method and override read(from:ofType:) instead.
        // If you do, you should also override isEntireFileLoaded to return false if the contents are lazily loaded.
        
        // NB: FOR FILE LOADING/SAVING, YOU NEED TO CHANGE APP SANDBOX SETTINGS.
        //     ... Under Project > Target > Capabilities > App Sandbox > File Access > User Selected File > "Read/Write"
        
        Swift.print("About to read data of type \(typeName).")
        
        guard let archive = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [Employee]?
        else
        {
            let outError: NSError! = NSError(domain: "Migrator", code: 0, userInfo: [NSLocalizedDescriptionKey : "The file could not be read"])
            throw outError
        }
    employees = archive
    }


}