Finder and Notes have a peculiar behaviour that I am seeking to reproduce. The ‘flexible space’ in the NSToolbar seems to take the dimensions of the split view into account. For instance, the first group of buttons aligns on the left side with the right side of the sidebar. The second group of icons aligns with the right side of the first column. When I widen the sidebar, the toolbar items move along with it.
Is it possible to reproduce this?
With the solution provided by @KenThomases, I have implemented this as follows:
final class MainWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
window?.toolbar?.delegate = self
// Make sure that tracking is enabled when the toolbar is completed
DispatchQueue.main.async {
self.trackSplitViewForFirstFlexibleToolbarItem()
}
}
}
extension MainWindowController: NSToolbarDelegate {
func toolbarWillAddItem(_ notification: Notification) {
// Make sure that tracking is evaluated only after the item was added
DispatchQueue.main.async {
self.trackSplitViewForFirstFlexibleToolbarItem()
}
}
func toolbarDidRemoveItem(_ notification: Notification) {
trackSplitViewForFirstFlexibleToolbarItem()
}
/// - Warning: This is a private Apple method and may break in the future.
func toolbarDidReorderItem(_ notification: Notification) {
trackSplitViewForFirstFlexibleToolbarItem()
}
/// - Warning: This method uses private Apple methods that may break in the future.
fileprivate func trackSplitViewForFirstFlexibleToolbarItem() {
guard var toolbarItems = self.window?.toolbar?.items, let splitView = (contentViewController as? NSSplitViewController)?.splitView else {
return
}
// Add tracking to the first flexible space and remove it from the group
if let firstFlexibleToolbarItem = toolbarItems.first, firstFlexibleToolbarItem.itemIdentifier == NSToolbarFlexibleSpaceItemIdentifier {
_ = firstFlexibleToolbarItem.perform(Selector(("setTrackedSplitView:")), with: splitView)
toolbarItems.removeFirst()
}
// Remove tracking from other flexible spaces
for flexibleToolbarItem in toolbarItems.filter({ $0.itemIdentifier == NSToolbarFlexibleSpaceItemIdentifier }) {
_ = flexibleToolbarItem.perform(Selector(("setTrackedSplitView:")), with: nil)
}
}
}
When using macOS 11 or newer, you can insert NSTrackingSeparatorToolbarItem
items to the toolbar, which will split up your toolbar in sections, aligned with the dividers of a NSSplitView
object.
This example adds the new separator items to a toolbar that already contains the rest of the buttons, configured in Interface Builder or in code. The target splitview concerns a standard configuration of 3 splitviews, including a sidebar panel.
class WindowController: NSWindowController, NSToolbarDelegate {
let mainPanelSeparatorIdentifier = NSToolbarItem.Identifier(rawValue: "MainPanel")
override func windowDidLoad() {
super.windowDidLoad()
self.window?.toolbar?.delegate = self
// Calling the inserts async gives more time to bind with the split viewer, and prevents crashes
DispatchQueue.main.async {
// The .sidebarTrackingSeparator is a built-in tracking separator which always aligns with the sidebar splitview
self.window?.toolbar?.insertItem(withItemIdentifier: .sidebarTrackingSeparator, at: 0)
// Example of a custom mainPanelSeparatorIdentifier
// Index at '3' means that there are 3 toolbar items at the left side
// of this separator, including the first tracking separator
self.window?.toolbar?.insertItem(withItemIdentifier: mainPanelSeparatorIdentifier at: 3)
}
}
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
if let splitView = (self.contentViewController as? NSSplitViewController)?.splitView {
// You must implement this for custom separator identifiers, to connect the separator with a split view divider
if itemIdentifier == mainPanelSeparatorIdentifier {
return NSTrackingSeparatorToolbarItem(identifier: itemIdentifier, splitView: splitView, dividerIndex: 1)
}
}
return nil
}
}
If you want to add an extra separator, for example for an Inspector panel, simply insert an additional toolbar item identifier to the toolbar, and assign an extra NSTrackingSeparatorToolbarItem
to another divider in the itemForItemIdentifier
delegate function.