I like to build an app in Swift 3 with Xcode 8 and it should enable the Apple tab bar. It is not document-based. I learned here, that the tabs can be enabled if I override t
Conceptually, this is what happens:
NSWindow.addTabbedWindow(_:ordered:)
to add a window to the native tab bars. NSResponder.newWindowForTab(_:)
into the responder chain of the main window, the "+" button will be visible.window.tabbingMode = .preferred
, the tab bar will always be visible.However, there are some caveats when implementing these methods.
newWindowForTab
So where to add @IBAction override func newWindowForTab(_ sender: Any?)
so you can call NSWindow.addTabbedWindow(_:ordered:)
?
NSWindowController
subclass you own. That's the simplest way to get to an NSWindow
to call addTabbedWindow
.AppDelegate
will have a reference to the main window. You can put the method here.NSWindow
instance.TL;DR: When you initialize a new window, store the window's windowController
somewhere. You need to maintain a strong reference in order for window events from being handled (in the controller).
I wrote a sample app with a TabManager that takes care of this: https://github.com/DivineDominion/NSWindow-Tabbing
And a blog post with details: https://christiantietze.de/posts/2019/07/nswindow-tabbing-multiple-nswindowcontroller/
Take into account how events are dispatched. Main Menu messages are sent down the responder chain, and so is newWindowForTab
. NSApp.sendAction
will fail for standard events if the source of the call doesn't connect up all the way -- that means, at least up to your NSWindowController
, maybe even up to your AppDelegate
.
You have to make sure any additional window you add is, in fact, part of the same responder chain as the original window, or else the menu items will stop working (and be greyed-out/disabled). Similarly, the "+" button stops to work when you click on it.
This is what @JohnV in the comments of the other answer called: "without the subview variable, you can't create more than two tabs". That's the effect, but it's not a real explanation. You can always create more tabs, but only from the original window/tab, not the new one; that's because the other tab is not responding to newWindowForTab
.
"The other tab" itself is just an NSWindow
. Your newWindowForTab
implementation resides in the controller, though. That's up one level.
Adapting the code by @Peter Ahlberg, this will work:
class WindowController: NSWindowController {
@IBAction override func newWindowForTab(_ sender: Any?) {
let windowController: WindowController = self.storyboard?.instantiateInitialController() as! WindowController
let newWindow = windowController.window
self.window?.addTabbedWindow(newWindow, ordered: .above)
newWindow.orderFront(self.window)
newWindow.makeKey()
// Store the windowController in a collection of sorts
// to keep a strong reference and make it handle events:
// (NSApp.delegate as? AppDelegate).addManagedWindowController(windowController)
}
}
I didn't need to add newWindowForTab
to AppDelegate
to make everything work using Storyboards -- because this way the window controllers keep doing their job and don't need a fallback!