Reputation: 12207
I'm building a toolbar that I want to look at least somewhat like Preview's. I want to create the Previous/Next buttons. The way I THINK it's done is with a segmented control, each has an image in the control. The problem is that I can't figure out how to get the labels underneath. I can get ONE label centered under the whole thing, but labeling the segmented control, But I can't get a Next and a Previous label under each part of the control.
What am I missing here?
thanks.
Upvotes: 3
Views: 3236
Reputation: 2932
As @frédéric-blanc pointed out: the only way to achieve this is with NSToolbarItemGroup
programmatically.
The trick is to assign both a view
and a subitems
array to the NSToolbarItemGroup
.
let group = NSToolbarItemGroup(itemIdentifier: NSToolbarItem.Identifier(rawValue: "NavigationGroupToolbarItem"))
let itemA = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "PrevToolbarItem"))
itemA.label = "Prev"
let itemB = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "NextToolbarItem"))
itemB.label = "Next"
let segmented = NSSegmentedControl(frame: NSRect(x: 0, y: 0, width: 85, height: 40))
segmented.segmentStyle = .texturedRounded
segmented.trackingMode = .momentary
segmented.segmentCount = 2
// Don't set a label: these would appear inside the button
segmented.setImage(NSImage(named: NSImage.goLeftTemplateName)!, forSegment: 0)
segmented.setWidth(40, forSegment: 0)
segmented.setImage(NSImage(named: NSImage.goRightTemplateName)!, forSegment: 1)
segmented.setWidth(40, forSegment: 1)
// `group.label` would overwrite segment labels
group.paletteLabel = "Navigation"
group.subitems = [itemA, itemB]
group.view = segmented
If you want to dabble with it, see the sample app code below.
This is all you need to test this in a new macOS/Cocoa sample app:
WindowController.swift
and paste this code into it.WindowController
.import Cocoa
class WindowController: NSWindowController, NSToolbarDelegate {
var _toolbar: NSToolbar!
let toolbarItems: [[String: Any]] = [
["title" : "irrelevant :)", "icon": "NSPreferencesGeneral", "identifier": NSToolbarItem.Identifier(rawValue: "NavigationGroupToolbarItem")],
["title" : "Share", "icon": NSImage.shareTemplateName, "identifier": NSToolbarItem.Identifier(rawValue: "ShareToolbarItem")],
["title" : "Add", "icon": NSImage.addTemplateName, "identifier": NSToolbarItem.Identifier(rawValue: "AddToolbarItem")]
]
var toolbarTabsIdentifiers: [NSToolbarItem.Identifier] {
return toolbarItems.compactMap { $0["identifier"] as? NSToolbarItem.Identifier }
}
override func windowDidLoad() {
_toolbar = NSToolbar(identifier: "TheToolbarIdentifier")
_toolbar.allowsUserCustomization = true
_toolbar.delegate = self
window?.toolbar = _toolbar
}
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
guard let infoDictionary: [String : Any] = toolbarItems.filter({ $0["identifier"] as? NSToolbarItem.Identifier == itemIdentifier }).first
else { return nil }
let toolbarItem: NSToolbarItem
if itemIdentifier == NSToolbarItem.Identifier(rawValue: "NavigationGroupToolbarItem") {
let group = NSToolbarItemGroup(itemIdentifier: itemIdentifier)
let itemA = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "PrevToolbarItem"))
itemA.label = "Prev"
let itemB = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "NextToolbarItem"))
itemB.label = "Next"
let segmented = NSSegmentedControl(frame: NSRect(x: 0, y: 0, width: 85, height: 40))
segmented.segmentStyle = .texturedRounded
segmented.trackingMode = .momentary
segmented.segmentCount = 2
// Don't set a label: these would appear inside the button
segmented.setImage(NSImage(named: NSImage.goLeftTemplateName)!, forSegment: 0)
segmented.setWidth(40, forSegment: 0)
segmented.setImage(NSImage(named: NSImage.goRightTemplateName)!, forSegment: 1)
segmented.setWidth(40, forSegment: 1)
// `group.label` would overwrite segment labels
group.paletteLabel = "Navigation"
group.subitems = [itemA, itemB]
group.view = segmented
toolbarItem = group
} else {
toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: itemIdentifier.rawValue))
toolbarItem.label = (infoDictionary["title"] as? String)!
let iconImage = NSImage(named: (infoDictionary["icon"] as? String)!)
let button = NSButton(frame: NSRect(x: 0, y: 0, width: 40, height: 40))
button.title = ""
button.image = iconImage
button.bezelStyle = .texturedRounded
toolbarItem.view = button
}
return toolbarItem
}
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return self.toolbarTabsIdentifiers
}
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return self.toolbarDefaultItemIdentifiers(toolbar)
}
func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return self.toolbarDefaultItemIdentifiers(toolbar)
}
func toolbarWillAddItem(_ notification: Notification) {
print("toolbarWillAddItem", (notification.userInfo?["item"] as? NSToolbarItem)?.itemIdentifier ?? "")
}
func toolbarDidRemoveItem(_ notification: Notification) {
print("toolbarDidRemoveItem", (notification.userInfo?["item"] as? NSToolbarItem)?.itemIdentifier ?? "")
}
}
Upvotes: 6
Reputation: 57
Brian, you may not have seen my n-th comment (sic) to my initial answer (since Stack Overflow hides them in a collapsable section when there are too many of them). But my last comment answered your latest question (i.e. the one re your inability to build an NSToolbarItemGroup from within Interface Builder).
Now that Snow Leopard is released, I can tell you you're still not able to graphically solve your issue by dragging some control from this newer IB's palette. Nope, your only solution is IMHO, as quoted from my Aug. 27 comment, "to programmatically allocate, initialize, and configure your group before adding it to your toolbar instance."
I hope you'll see this new answer ('cause I do think you simply didn't notice my hidden comment…). Wishing you best of luck, hoping you'll be able to solve your problem with those hints, the needed code still being in the Overview section of the NSToolbarItemGroup class reference.
Upvotes: 1
Reputation: 57
I'm pretty sure it's a "hack"… In Interface Builder (IB), I've managed to recreate the look you'd like to achieve by using, as mentioned, an NSSegmentedControl instance w/ the "Capsule" style. But for its label, I do think it's "just" the string "Previous Next"… (Yep, w/ some space characters as separators :( !)
EDIT: The right answer can be found in the comments; i.e. from Leopard, one can use the NSToolbarItemGroup class to achieve such a result with a segmented control-based toolbar item, as illustrated by code in the documentation here.
(Sorry for the confusion, but as I'm new to Stack Overflow, I don't know whether it's better to remove my comments—and edit my answer—, or leave them for history purposes…)
Upvotes: 1