Binding managedObjectContext, Xcode 8.3.2, Storyboards, macOS, Swift

Any sophisticated ways to accomplish the main assignment of the chapter 13. Basic Core Data with Xcode 8.3.2?

As you might know, Xcode 8.3.2 for macOS application offers the only Storyboards, no more NIB.
The following way seems to work well, but is it a proper way? Any other nicer ways?

Begin with the chapter 13. Basic Core Data.

Be sure to create a new target or new project with:

  • Create Document-Based Application
  • Use Core Data

Open the template file Document.swift and insert one line at the very end.

class Document: NSPersistentDocument {
...
  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)

    // To let the View Controller (i.e. File's Owner) know this instance.
    windowController.contentViewController?.representedObject = self
  }

Keep going and prepare the model.

In the section Configure Array Controller, fill the Model Key Path with
representedObject.managedObjectContext, instead of managedObjectContext.

Go through the rest of the chapter.

Additional information:

(1) For both Vertical and Horizontal Split View Controllers, relay representedObject to their children.

class SplitViewController: NSSplitViewController {
...
  override var representedObject: Any? {
    didSet {
      splitViewItems.forEach {
        $0.viewController.representedObject = representedObject
      }
    }
  }

(2a) For Segues connecting to a Window Controller, hand representedObject to the destination view controller by choosing this subclass in the Segue’s Attribute inspector.

class ToWindowControllerStoryboardSegue: NSStoryboardSegue {
  override func perform() {
    super.perform()
    (destinationController as? NSWindowController)?.contentViewController?.representedObject =
      (sourceController as? NSViewController)?.representedObject
  }
}

(2b) For Segues connecting to a View Controller.

class ToViewControllerStoryboardSegue: NSStoryboardSegue {
  override func perform() {
    super.perform()
    (destinationController as? NSViewController)?.representedObject =
      (sourceController as? NSViewController)?.representedObject
  }
}

Any additional information will be appreciated!

UPDATES:

It would be better if the representedObject was propagated to the destination window controller before calling super.perform() in a subclass of NSStoryboardSegue.

class ToWindowControllerStoryboardSegue: NSStoryboardSegue {
  override func perform() {
    (destinationController as? NSWindowController)?.contentViewController?.representedObject =
      (sourceController as? NSViewController)?.representedObject
    super.perform()
  }
}

class ToViewControllerStoryboardSegue: NSStoryboardSegue {
  override func perform() {
    (destinationController as? NSViewController)?.representedObject =
      (sourceController as? NSViewController)?.representedObject
    super.perform()
  }
}

What I have found is that during the call of super.perform(), mentioned above, a destination window controller’s viewWillAppear() will be called.

So, if we want to manually fetch entities before the window appears, we can do that in a simple way like this:

class DestinationViewController: NSViewController {

  override func viewWillAppear() {
    super.viewWillAppear()
    fetch()
  }

  func fetch() {
    guard let document = representedObject as? NSPersistentDocument else { return }
    guard let context = document.managedObjectContext else { return }

    do {
      let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Entity Name")
      let results = try context.fetch(request)
      // work with the results
    } catch {
      // error
    }
  }

}

If we use NSArrayController binding to representedObject.managedObjectContext, instead, the order of assignment of representedObject and super.perform() does not matter. It seems that instances of NSArrayController observe representedObject and when the object is set, they will automatically start fetching their own entity.

Confirmed with Xcode 9 Beta.

Thanks.

UPDATES:

To activate File > Save and Edit > Undo menu items in the destination window opened by segue, it seems to need to call addWindowController(_ windowController: NSWindowController).

class ToWindowControllerStoryboardSegue: NSStoryboardSegue {

  override func perform() {
    let src = sourceController as! NSViewController
    let dst = destinationController as! NSWindowController
    dst.contentViewController?.representedObject = src.representedObject

    let doc = src.representedObject as! NSDocument  // or Document
    doc.addWindowController(dst)

    super.perform()
  }

}