I had the same problem and just found a solution using storyboard and Swift 3.1
I basically used the ViewController as a man in the middle solution. The difficult part was to discover how to get a reference to the Document in the ViewController and to the ViewController in the Document.
You need to create the IBOutlets and IBAction on the ViewController.swift and connect them to the interface.
Follows my ViewController code:
import Cocoa
class ViewController: NSViewController {
@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var arrayController: NSArrayController!
@IBAction func addEmployee(_ sender: NSButton) {
let document = NSDocumentController.shared().currentDocument as! Document
document.addEmployee(sender)
}
}
See that NSDocumentController.shared().currrentDocument gives you a reference to the Document class where you can trigger the proper method (addEmployee).
Follows the relevant code in Document class:
create a variable to hold the reference to the ViewController
class Document: NSDocument, NSWindowDelegate {
var vc = ViewController()
In the makeWindowControllers() method add the line:
vc = windowController.contentViewController as! ViewController
Finally add the function as described in the book using vc to access the arrayController and tableView outlet in the ViewControler
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 occured 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 = vc.arrayController.newObject() as! Employee
// Add it to the array controller's contentArray
vc.arrayController.addObject(employee)
// re-sort (in case the user has sorted a column)
vc.arrayController.rearrangeObjects()
// Get the sorted array
let sortedEmployees = vc.arrayController.arrangedObjects as! [Employee]
let row = sortedEmployees.index(of: employee)!
// Begin the edit in the first column
Swift.print("starting edit of \(employee) in row \(row)")
vc.tableView.editColumn(0, row: row, with: nil, select: true)
}
Full Document class implementation:
import Cocoa
private var KVOContext: Int = 0
class Document: NSDocument, NSWindowDelegate {
var vc = ViewController()
var employees: [Employee] = [] {
willSet {
for employee in employees {
stopObservingEmployee(employee)
}
}
didSet {
for employee in employees {
startObservingEmployee(employee)
}
}
}
override init() {
super.init()
// Add your subclass-specific initialization here.
}
override class func autosavesInPlace() -> Bool {
return true
}
override func makeWindowControllers() {
// Returns the Storyboard that contains your Document window.
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let windowController = storyboard.instantiateController(withIdentifier: "Document Window Controller") as! NSWindowController
self.addWindowController(windowController)
windowController.contentViewController!.representedObject = windowController.document
windowController.window?.delegate = self
vc = windowController.contentViewController as! ViewController
}
override func data(ofType typeName: String) throws -> Data {
// Insert code here to write your document to data of the specified type. If outError != nil, ensure that you create and set an appropriate error when returning nil.
// You can also choose to override fileWrapperOfType:error:, writeToURL:ofType:error:, or writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
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. If outError != nil, ensure that you create and set an appropriate error when returning false.
// You can also choose to override readFromFileWrapper:ofType:error: or readFromURL:ofType:error: instead.
// If you override either of these, you should also override -isEntireFileLoaded to return false if the contents are lazily loaded.
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
// MARK: - Actions
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 occured 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 = vc.arrayController.newObject() as! Employee
// Add it to the array controller's contentArray
vc.arrayController.addObject(employee)
// re-sort (in case the user has sorted a column)
vc.arrayController.rearrangeObjects()
// Get the sorted array
let sortedEmployees = vc.arrayController.arrangedObjects as! [Employee]
let row = sortedEmployees.index(of: employee)!
// Begin the edit in the first column
Swift.print("starting edit of \(employee) in row \(row)")
vc.tableView.editColumn(0, row: row, with: nil, select: true)
}
// MARK: - Accessors
func insertObject(_ employee: Employee, inEmployeesAtIndex index: Int) {
Swift.print("adding \(employee) to the employees array")
// Add the inverse of this operation to the undo stack
let undo: UndoManager = undoManager!
(undo.prepare(withInvocationTarget: self) as AnyObject).removeObjectFromEmployeesAtIndex(employees.count)
if !undo.isUndoing {
undo.setActionName("Add Person")
}
employees.append(employee)
}
func removeObjectFromEmployeesAtIndex(_ 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")
}
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 intented for our superclass.
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if let keyPath = keyPath, let object = object, let change = change {
var oldValue: AnyObject? = change[NSKeyValueChangeKey.oldKey] as AnyObject
if oldValue is NSNull {
oldValue = nil
}
let undo: UndoManager = undoManager!
Swift.print("oldValue= \(oldValue!)")
(undo.prepare(withInvocationTarget: object) as AnyObject).setValue(oldValue, forKeyPath: keyPath)
}
}
// MARK: - NSWindowDelegate
func windowWillClose(_ notification: Notification) {
Swift.print("Window will close. Observers Stopped!")
employees = []
}
}