Can't connect tableView to @IBOutlet in storyboards

In Xcode 8.3, ( with main.storyboard ) I cannot connect tableView outlet ( @IBOutlet weak var tableView: NSTableView! ) which is located in document.swift file, according to the book, to table view in Main.Storyboard.

In the chapter they write: „open document.swift, first add two new outlets … control click the File’s owner … and drag to connect the tableView outlet.” But there is no File’s owner anymore.

What am I forgetting to do?
Help

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 = []
 }  

}