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