macosnstoolbarnstabview

NSTabView: Toolbar does not display


macOS 10.12.6; Xcode 9.3, storyboards

I have an NSTabView (tabless) that in itself contains two NSTabViews. One is tabless, the other one uses the 'toolbar' style.

When I start my app with the toolbar visible, everything is fine: it displays my tabs in the toolbar, I can change them, etc etc. Once I change to the other branch of my storyboard, the toolbar disappears... and when I come back, instead of a toolbar proper, with buttons and all that, I get a slightly widened bar that has no content in it.

I've set up a sample project to show my problem, where - for ease of switching - I have left the other two tabViewControllers to show their tabs (bottom/top, but this makes no difference).

1) First run (starting with 'toolbar' branch): first run; toolbar visible 2) (not shown): switch to 'top' branch 3) After switching back to 'toolbar': return to branch, toolbar not displayed correctly

As a diagnostic aid, I've created a 'displayToolbarStatus' IBAction in the AppController:

@IBAction func displayToolbarStatus(_ sender: NSMenuItem){
    if let window = NSApplication.shared.windows.first {
        print(window.toolbar?.isVisible)
    }
}

The results are as follows: 1) optional(true) 2) nil 3) optional(true)

which is very much in line with how things should work: the toolbar exists and is displayed, there is no toolbar, the toolbar exists and is displayed. Only, of course, it is not usable as a toolbar. (turning visibility off and on, or trying to force a size change with window.toolbar?.sizeMode = .regular has no effect whatsoever, nor does assigning icons to the toolbar items; the toolbar remains squashed and without functioning buttons.

I haven't worked in any depth with NSToolbar: is this a known problem with a workaround; is this new to Xcode 9.2 (which, after all, thinks that no window is valid, so obviously has some problems in that field)?

I really want to use the NSTabView 'toolbar' functionality: how do I proceed?


Solution

  • I've now had more time to play with toolbars. The 'weird' appearance of the non responsive toolbar is simply an empty toolbar, which gave me a clue as to what was going on.

    0) The NSTabView overrides the window's toolbar; it does not hand back control when it vanishes; this means that if you have another toolbar in your window that will never show up the moment you're using an NSTabView with 'toolbar' style.

    1) I have added a print statement to every relevant method in the ToolbarTabViewController and a 'Switching Tabs' in the containing TabViewController's DidSelect TabViewItem, as well as logging when Toolbar items are added to the window. (The ToolbarTabViewController is the second controller in the containing TabViewController; it is selected. Otherwise the stack looks slightly different):

    ViewDidLoad 
    Switching tabs 
    viewWillAppear 
    viewDidAppear 
    Switching tabs
    Toolbar will add item 
    Toolbar will add item 
    viewWillAppear
    viewDidAppear
    

    Switching away to the other tab:

    viewWillDisappear
    Switching tabs
    Toolbar did remove item
    Toolbar did remove item
    viewDidDisappear
    

    So far, so good.

    Switching back to the ToolbarTabController, we get

    viewWillAppear
    Switching tabs
    viewDidAppear
    

    Whatever method is called that adds the tabs-related items to the toolbar on first appearance does never get called again. (Note also that the the order of switching tabs and viewDidAppear is not consistent between the first and subsequent appearances.)

    2) So, the logical thing to do seems to be to capture the items that are being created and to add them back for future iterations. In the ToolbarTabViewController:

     var defaultToolbarItems: [NSToolbarItem] = []
    
        @IBAction  func addTabsBack(_ sender: Any){
                if let window = NSApplication.shared.windows.first {
                    if let toolbar = window.toolbar{
                        for (index, item) in defaultToolbarItems.enumerated() {
                            toolbar.insertItem(withItemIdentifier: item.itemIdentifier, at: index)
                        }
                    }
                }
            }
    
    override func toolbarWillAddItem(_ notification: Notification) {
           // print("Toolbar will add item")
            if let toolbarItem = notification.userInfo?["item"] as? NSToolbarItem {
                if defaultToolbarItems.count < tabView.numberOfTabViewItems{
                     defaultToolbarItems.append(toolbarItem)
                }
            }
        }
    

    3) The last question was when (and where) to call addTabsBack() - I found that if I try to call it in viewWillAppear I start out with four toolbarItems, though the number of tabViewItems is 2. (and they do, in fact, seem to be duplications: same name, same functionality). Therefore, I am calling addTabsBack()in the surrounding TabViewController's 'didSelect TabViewItem' method - willSelect is too early; but didSelect gives me exactly the functionality I need.

    4) There probably is a more elegant way of capturing the active toolbarItems, but for now, I have a working solution.