How does one display a new view controller in the same Mac window?

(Reviving this as it comes up as first relevant result on Google and I had the same problem but decided against a custom segue)

While custom segues work (at least, the code given in foundry's answer worked under Swift 3; it needs updating for Swift 4), the sheer amount of work involved in writing a custom animator suggests to me that their main use case is custom animations.

The simple solution to changing the content of a window is to create an NSWindowController for your window, and to set its contentViewController to the desired viewController. This is particularly useful if you are following the typical pattern of storyboards and instantiate a new ViewController instance every time you switch.

However.

The NSStoryboard documentation says, quite clearly in macOS, containment (rather than transition) is the more common notion for storyboards which led me to look again at the available tools.

You could use a container view for this task, which adds a NWViewController layer instead of the NSWindowController outlined above. The solution I've gone with is to use an NSTabViewController. In the attributes inspector, set the style to 'unspecified'TabViewController settings, then select the TabView and set its style to 'tabless'. TabView selectionTabView Settings

To change tabs programatically, you set the selectedTabViewItemIndexof your TabViewController.

This solution reuses the same instance of the ViewControllers for the tab content, so that any data entered in text fields is preserved when the user switches to the other 'tab'.


If you provide a custom segue (subclass of NSStoryboardSegue) you can get the result you are after. There are a few gotchas with this approach though:

  • the custom segue will use presentViewController:animator so you will need to provide an animator object
  • because the presented view is not backed by a separate Window object, you may need to provide it with a custom NSView just to catch out mouse events that you don't want to propagate to the underlying NSViewController's view
  • there's also a Swift-only glitch regarding the custom segue's identifier property you need to watch out for.

As there doesn't seem to be much documentation about this I have made a small demo project with custom segue examples in Swift and Objective-C.

enter image description here

I also have provided some more detail in answer to this question.


Simple way with no segues involved to replace the current view controller in the same window:

if let myViewController = self.storyboard?.instantiateController(withIdentifier: "MyViewController") as? MyViewController {
    self.view.window?.contentViewController = myViewController
}

Tags:

Macos

Cocoa

Swift