12

In Big Sur, Xcode and Calendar have toolbar items that stay over the sidebar when open but remain visible on the left side when the sidebar's collapsed.

Sidebar open: enter image description here

Sidebar collapsed: enter image description here

In "Adopt the New Look of macOS" at 13:55, John says "items placed before the separator [sidebarTrackingSeparator] will appear over the full-height sidebar", just as they are in Xcode and Calendar. I haven't been able to make this work.

Here's a sample project that demonstrates the issue. I used the IB-defined "Window Controller with Sidebar" and added a toolbar item for toggling the sidebar. In a subclass of NSWindowController I insert .sidebarTrackingSeparator after the .toggleSidebar item:

override func windowDidLoad() {
    // Sometimes the toolbar items aren't loaded yet--async is a quick and dirty way to prevent a crash
    DispatchQueue.main.async {
        self.window?.toolbar?.insertItem(withItemIdentifier: .sidebarTrackingSeparator, at: 1)
    }
}

Sometimes this has no effect (the toggle button remains to the right of the sidebar). Sometimes the sidebar toggle get put in an overflow menu:

enter image description here

I haven't seen any discussion of implementing this toolbar design outside that WWDC session. Has anyone been able to get this to work?

smr
  • 890
  • 7
  • 25

1 Answers1

13

This is a IB/Code timing disagreement. Interface Builder configures and installs the toolbar before you add the .sidebarTrackingSeparator toolbar item.

So you're doing the right thing, just too late. And too later with the dispatch. I think the important thing is to have the item in there before the toolbar is set on the window.

Unfortunately, that isn't really possible with IB, unless I believe, you create a whole new toolbar and reassign it. But that's a bad idea, because then you may run into trouble auto-saving the state of your toolbar.

The trick is to configure the separator in Interface Builder. If you look at the ObjC documentation for this constant, you'll see a longer name: NSToolbarSidebarTrackingSeparatorItemIdentifier.

The best we can do here is hope that the symbol's name is the same value as the identifier. If you really want to verify this, you can just print the symbol's value in the debugger:

(lldb) po NSToolbarSidebarTrackingSeparatorItemIdentifier
NSToolbarSidebarTrackingSeparatorItemIdentifier

If we create a custom toolbar item in IB, and add that according to John's video...

Interface Builder with a custom toolbar item positioned in the toolbar. The item has the identifier NSToolbarSidebarTrackingSeparatorItemIdentifier set in the attributes inspector

low and behold: enter image description here

nteissler
  • 1,513
  • 15
  • 16
  • 3
    Using NSToolbarDelegate is an alternative – Wesley McCloy Sep 23 '20 at 01:34
  • 1
    Oh, nice I didn't know that. An NSToolbarDelegate seems to easily augment IB created toolbars, and the docs say as much. Thank you! – nteissler Sep 23 '20 at 21:05
  • This does solve the strange behavior, and it allows items over the sidebar. I wish the toggle button were inside the sidebar when expanded, but that's a minor nit. Interestingly, items placed in the sidebar can be removed by the user, but they can't be put back. Will file FB shortly. Thanks for the help with this! – smr Sep 25 '20 at 00:20
  • @WesleyMcCloy: Do you have any guidance how to do this with `NSToolbarDelegate`? If yes, could add this as a distinct answer? Thanks a lot! – Maschina Jan 10 '21 at 17:51
  • @Maschina, I'm fuzzy on the details, but the gist is that `NSToolbarDelegate` provides the same functionality as IB in terms of listing the allowed and default toolbar items. This is done via `toolbarAllowedItemIdentifiers(_:)` and `toolbarDefaultItemIdentifiers(_:)`. You can further adopt `NSToolbarDelegate` if you want to programmatically create your `NSToolbarItem`s. – Wesley McCloy Jan 13 '21 at 21:08
  • 4
    Adding to the answer by @nteissler, the system automatically places a sidebar item on the right of the sidebar divider (in a left-to-right language). To put it inside the sidebar like other items, you can create a custom item using the same SF Symbol (sidebar.leading) and use `NSSplitViewController.toggleSidebar` as its action. Works as of macOS 11.2.2 – smr Mar 25 '21 at 19:15
  • @smr oof, I have wasted a few hours before I saw your last comment – undocumented behavior changes are so bad. Meanwhile, Xcode has this button inside the sidebar, right near the traffic lights buttons. >:( By the way, I solved the problem by adding an empty NSToolbar to a window in a storyboard, and then populating it via [`insertItem(withItemIdentifier:at:)`](https://developer.apple.com/documentation/appkit/nstoolbar/1516941-insertitem) from the toolbar's delegate. – Ivan Mir Jul 11 '21 at 06:00
  • @smr You truly saved my day! Didn't know ```NSSplitViewController.toggleSidebar``` can be invoked directly by a ```#selector```! – RoyRao Dec 17 '21 at 05:57