You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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

额外注意事项

  1. 清理旧配置:由于NSToolbar会自动保存用户配置,测试前需要删除应用的偏好设置(或在代码中调用toolbar.removeAllItems()并重置配置),避免旧缓存影响结果。
  2. 视图尺寸约束:确保自定义NSView有明确的尺寸约束(如示例中ThumbnailSizeView的子视图约束已覆盖所有边距),保证在自定义面板中预览时显示正常。
  3. 验证代理方法:确认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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 16:59:50