自定义NSView子类的NSToolbarItem在自定义工具栏面板不显示问题
解决自定义NSView作为NSToolbarItem视图时无法在自定义面板常用项区域显示的问题
问题场景
在AppKit应用中通过代码创建带自定义NSView子类的NSToolbarItem时,工具栏项正常使用时显示和功能都没问题,但进入工具栏自定义模式后:
- 该自定义项仅能在面板底部的“或将默认集拖入工具栏”区域显示
- 无法出现在顶部的“将常用项拖入工具栏”区域
而使用NSButton、NSTextField等标准控件作为视图的工具栏项则能在两个区域正常显示。
问题原因
macOS的工具栏自定义面板顶部的“常用项”区域,要求每个工具栏项提供可用于展示的缩略图/图标:
- 对于NSButton这类标准控件,系统会自动提取或生成对应的表示图像
- 自定义NSView子类时,系统无法自动生成合适的展示图标,因此不会将其纳入常用项列表,仅保留在默认集合中。
解决方案
给自定义视图对应的NSToolbarItem设置image属性,同时确保视图有明确的尺寸约束(保证自定义面板中预览正常)。
修改后的关键代码
在创建thumbnailSize工具栏项时,添加image配置:
case ToolbarItemIdentifiers.thumbnailSize: let toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier) toolbarItem.label = "Thumbnail Size" // 新增:设置工具栏项的展示图标,用于自定义面板常用项区域 toolbarItem.image = NSImage(systemSymbolName: "slider.horizontal", accessibilityDescription: "Thumbnail Size Slider") // 可选:设置自定义面板中的显示标签,优化体验 toolbarItem.paletteLabel = "Adjust Thumbnail Size" // 初始化自定义视图时传入合适的默认值(补全原代码的参数) toolbarItem.view = ThumbnailSizeView(min: 10, max: 200, initial: 80) return toolbarItem
额外注意事项
- 清理旧配置:由于NSToolbar会自动保存用户配置,测试前需要删除应用的偏好设置(或在代码中调用
toolbar.removeAllItems()并重置配置),避免旧缓存影响结果。 - 视图尺寸约束:确保自定义NSView有明确的尺寸约束(如示例中
ThumbnailSizeView的子视图约束已覆盖所有边距),保证在自定义面板中预览时显示正常。 - 验证代理方法:确认
toolbarAllowedItemIdentifiers已包含该工具栏项的标识符,确保它是允许被自定义的。
完整修改后的代码
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.image = NSImage(systemSymbolName: "slider.horizontal", accessibilityDescription: "Thumbnail Size Slider") // 可选:自定义面板中的标签 toolbarItem.paletteLabel = "Adjust Thumbnail Size" // 补全自定义视图的初始化参数 toolbarItem.view = ThumbnailSizeView(min: 10, max: 200, initial: 80) 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 ] } @objc private func addPhotosButtonTapped() { // 实现按钮点击逻辑 } } 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") } }
内容的提问来源于stack exchange,提问作者Jaysen Marais
相关产品推荐
相关产品推荐

