I have created some NSToolbarItem
s programmatically with custom views in an AppKit app. The toolbar items are appearing (and functioning) just fine in the toolbar. The issue arises when the toolbar is being customised.
The NSToolbarItem
with NSView
-subclass custom view does render in toolbar and the "... or drag the default set into the toolbar" area of the customisation panel (the bottom bit), but not in the "Drag your favourite items into the toolbar ..." area of the customisation panel (the top bit). The same code generates all 3 NSToolbarItem
instances.
Toolbar items using NSButton
, NSTextField
and other controls as custom views do render in all case, but not those based on NSView
. What do I need to do to make NSView
-based custom toolbar items visible in both areas of the customise toolbar pane?
Here's a cut-down version of the NSWindowController
which creates the toolbar (and acts as the toolbar's delegate)
import AppKit
public class DocumentWindowController : NSWindowController, NSToolbarDelegate {
private struct ToolbarItemIdentifiers {
fileprivate static let addPhotos = NSToolbarItem.Identifier("add-photos")
fileprivate static let thumbnailSize = NSToolbarItem.Identifier("thumbnail-size")
}
public override init (
window: NSWindow?
) {
super.init(window: window)
let toolbar = NSToolbar(identifier: "toolbar-identifier.document")
toolbar.delegate = self
toolbar.allowsUserCustomization = true
toolbar.autosavesConfiguration = true
toolbar.displayMode = .default
window?.toolbarStyle = .unified
window?.titleVisibility = .visible
window?.toolbar = toolbar
}
// MARK: - NSToolbarDelegate
public func toolbar (
_ toolbar: NSToolbar,
itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
willBeInsertedIntoToolbar flag: Bool
) -> NSToolbarItem? {
switch itemIdentifier {
case ToolbarItemIdentifiers.addPhotos:
let toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier)
toolbarItem.label = "Add Photos"
toolbarItem.image = NSImage(systemSymbolName: "plus", accessibilityDescription: "Add Photos Icon")
toolbarItem.target = self
toolbarItem.action = #selector(self.addPhotosButtonTapped)
return toolbarItem
case ToolbarItemIdentifiers.thumbnailSize:
let toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier)
toolbarItem.label = "Thumbnail Size"
toolbarItem.view = ThumbnailSizeView() // an unremarkable NSView subclass
return toolbarItem
default:
return nil
}
}
public func toolbarAllowedItemIdentifiers (_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [
ToolbarItemIdentifiers.addPhotos,
ToolbarItemIdentifiers.thumbnailSize
]
}
public func toolbarDefaultItemIdentifiers (_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
return [
ToolbarItemIdentifiers.addPhotos,
ToolbarItemIdentifiers.thumbnailSize
]
}
}
class ThumbnailSizeView : NSView {
private let slider = NSSlider()
init(min minValue: Int64, max maxValue: Int64, initial initialValue: Int64) {
super.init(frame: .zero)
let minusIcon = NSImageView()
minusIcon.image = NSImage(
systemSymbolName: "minus",
accessibilityDescription: "Smaller"
)?.withSymbolConfiguration(.init(scale: .small))
minusIcon.translatesAutoresizingMaskIntoConstraints = false
minusIcon.setContentCompressionResistancePriority(.required, for: .horizontal)
minusIcon.setContentHuggingPriority(.required, for: .horizontal)
addSubview(minusIcon)
NSLayoutConstraint.activate([
minusIcon.leadingAnchor.constraint(equalTo: leadingAnchor),
minusIcon.centerYAnchor.constraint(equalTo: centerYAnchor)
])
slider.minValue = Double(minValue)
slider.maxValue = Double(maxValue)
slider.intValue = Int32(initialValue)
slider.sliderType = .linear
slider.controlSize = .mini
slider.translatesAutoresizingMaskIntoConstraints = false
addSubview(slider)
NSLayoutConstraint.activate([
slider.leadingAnchor.constraint(equalTo: minusIcon.trailingAnchor, constant: 4),
slider.centerYAnchor.constraint(equalTo: centerYAnchor),
slider.widthAnchor.constraint(equalToConstant: 66)
])
let plusIcon = NSImageView()
plusIcon.image = NSImage(
systemSymbolName: "plus",
accessibilityDescription: "Larger"
)?.withSymbolConfiguration(.init(scale: .small))
plusIcon.translatesAutoresizingMaskIntoConstraints = false
plusIcon.setContentCompressionResistancePriority(.required, for: .horizontal)
plusIcon.setContentHuggingPriority(.required, for: .horizontal)
addSubview(plusIcon)
NSLayoutConstraint.activate([
plusIcon.leadingAnchor.constraint(equalTo: slider.trailingAnchor, constant: 4),
plusIcon.centerYAnchor.constraint(equalTo: centerYAnchor),
plusIcon.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
For context, I'm using Xcode 14.3.1 on macOS Ventura 13.5.1
The ThumbnailSizeView
is missing a height constraint and its height is zero. Add a height constraint to fix the issue.
NSLayoutConstraint.activate([
slider.leadingAnchor.constraint(equalTo: minusIcon.trailingAnchor, constant: 4),
slider.heightAnchor.constraint(equalTo: heightAnchor),
slider.widthAnchor.constraint(equalToConstant: 66)
])