How to add Tabs in a non-document-based app under macOS?

后端 未结 3 872
说谎
说谎 2021-01-12 06:44

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

相关标签:
3条回答
  • 2021-01-12 07:05

    Conceptually, this is what happens:

    • It suffices to call NSWindow.addTabbedWindow(_:ordered:) to add a window to the native tab bars.
    • Once you put NSResponder.newWindowForTab(_:) into the responder chain of the main window, the "+" button will be visible.
    • When you set window.tabbingMode = .preferred, the tab bar will always be visible.

    However, there are some caveats when implementing these methods.

    Implement newWindowForTab

    So where to add @IBAction override func newWindowForTab(_ sender: Any?) so you can call NSWindow.addTabbedWindow(_:ordered:)?

    • If you use Storyboards, then put this into a NSWindowController subclass you own. That's the simplest way to get to an NSWindow to call addTabbedWindow.
    • If you use Xibs, the AppDelegate will have a reference to the main window. You can put the method here.
    • If you use a programmatic setup, put it wherever you know the main NSWindow instance.

    Make the "+" Button Work

    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!

    0 讨论(0)
  • 2021-01-12 07:15

    In case above "+" will add new tab always after first window, if you close first window then it will be recreated under current window.

    there is way to get it work

    override func newWindowForTab(_ sender: Any?) {
        let wc: NSWindowController = self.storyboard?.instantiateInitialController() as! NSWindowController
        wc.window?.windowController = self
        NSApplication.shared.mainWindow!.addTabbedWindow(wc.window!, ordered: .above)
        wc.window?.orderFront(self)
    }
    

    trick in NSApplication.shared.mainWindow! you always add tab to current active window.

    and if we need create window at the end we should use this trick

    let tabbedWindows = NSApplication.shared.mainWindow!.tabbedWindows!
    let lastTabIdx = tabbedWindows.count - 1
    tabbedWindows[lastTabIdx].addTabbedWindow(wc.window!, ordered: .above)
    
    0 讨论(0)
  • 2021-01-12 07:17

    Ok here are new files,

    Appdelegate

    import Cocoa
    
    @NSApplicationMain
    class AppDelegate: NSObject, NSApplicationDelegate {
    
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application
    }
    
    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }
    
    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        return true
    }
    
    @IBAction func newWindowForTab(_ sender: Any?){
    } // without this the + button doesn't show from start
    
    }
    

    ViewController

    import Cocoa
    
    class ViewController: NSViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        // Do any additional setup after loading the view.
    }
    
    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }
    }
    

    and WindowController

    import Cocoa
    
    class WindowController: NSWindowController {
    
    var subview: WindowController?
    
    override func windowDidLoad() {
        super.windowDidLoad()
        // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
    }
    
    @IBAction override func newWindowForTab(_ sender: Any?) {
    
        let story = self.storyboard
        let windowVC: WindowController = story?.instantiateInitialController() as! WindowController
    
        self.window?.addTabbedWindow(windowVC.window!, ordered: .above)
        self.subview = windowVC
    
        windowVC.window?.orderFront(self.window)
        windowVC.window?.makeKey()
    }
    
    }
    

    you have to add menu item and connect it to FirstResponder in menu view to newWindowForTab: action, assign key, say cmd+t to work, this example as is just adds tab from + button and window menu options work, "move tab to new window" and "merge all windows". You can drag tab out and drop back , move tabs horizontally. Look like it works.

    done with Xcode Version 8.2 beta (8C30a)

    0 讨论(0)
提交回复
热议问题