31

I'm fairly new to Mac development and am slightly confused by the new "storyboard" feature in Xcode 6. What I'm trying to do is segue from one view controller to another in the same window. As of right now, all the different NSViewControllerSegues present the view controller in a new window, be it a modal or just another window. What I'd like to do is just segue within the same window, much in the same way one would on iOS (though an animated transition is not crucial). How would this be achieved?

aaplmath
  • 1,717
  • 1
  • 14
  • 27

3 Answers3

37

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.

Community
  • 1
  • 1
foundry
  • 31,615
  • 9
  • 90
  • 125
  • 1
    The example project your provided on GitHub is incredibly useful! Trying to adjust from iOS development to macOS development has not been easy for me, so thanks a lot for the code. One question, how can you do a push view controller animation on macOS? (Like the macOS Tweetbot app). – Supertecnoboff May 22 '17 at 08:40
  • 1
    The main issue with this solution is when you are trying to resize the window, the presented view controller won't resize as the parent view controller does. However it helps to understand how segue works compared to iOS segues. I recommend using @green_knight solution below which is effortless and quite elegant too – Olympiloutre Jul 24 '18 at 09:27
12

(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'.

green_knight
  • 1,319
  • 14
  • 26
12

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
}
Mark
  • 139
  • 2
  • 7