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

如何基于选择切换NSOutlineView的Storyboard上下文菜单?

动态切换NSOutlineView上下文菜单(基于Storyboard实现)

针对menuNeedsUpdate赋值报错的解决办法

你在menuNeedsUpdate(_:)里直接给menu赋值会报错,因为这个参数是只读的let常量。如果不想用隐藏菜单项的方式,你可以清空当前菜单,直接把目标菜单的所有项传进去:

extension ViewController: NSMenuDelegate {
    func menuNeedsUpdate(_ menu: NSMenu) {
        menu.removeAllItems()
        
        // 根据选中逻辑切换菜单
        if /* 选中了类型A的内容 */ {
            menu.items = menuOne.items
        } else if /* 选中了类型B的内容 */ {
            menu.items = menuTwo.items
        } else {
            menu.items = menuThree.items
        }
    }
}

这样就能复用你在Storyboard里配置好的菜单结构,不用重新写代码构建菜单。

子类化NSOutlineView后访问ViewController的IBOutlet菜单

如果你想通过子类化NSOutlineView重写menu(for:)来实现,有两种简单的方式访问ViewController里的菜单:

方式1:给自定义OutlineView加弱引用

在自定义类里加一个弱引用指向ViewController,避免循环引用:

class MyOutlineView: NSOutlineView {
    weak var menuSource: ViewController?
    
    override func menu(for event: NSEvent) -> NSMenu? {
        guard let source = menuSource else { return super.menu(for: event) }
        
        // 根据当前选中项判断返回哪个菜单
        let selectedRows = self.selectedRowIndexes
        if /* 选中项满足条件1 */ {
            return source.menuOne
        } else if /* 满足条件2 */ {
            return source.menuTwo
        } else {
            return source.menuThree
        }
    }
}

然后在Storyboard里把OutlineView的类改成MyOutlineView,再在ViewController的viewDidLoad里关联引用:

class ViewController: NSViewController {
    @IBOutlet var outlineView: MyOutlineView!
    @IBOutlet var menuOne: NSMenu!
    @IBOutlet var menuTwo: NSMenu!
    @IBOutlet var menuThree: NSMenu!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        outlineView.menuSource = self
    }
}

方式2:用闭包解耦

如果不想让自定义OutlineView依赖ViewController类型,可以用闭包传递逻辑:

class MyOutlineView: NSOutlineView {
    var getMenu: ((NSEvent) -> NSMenu?)?
    
    override func menu(for event: NSEvent) -> NSMenu? {
        return getMenu?(event) ?? super.menu(for: event)
    }
}

然后在ViewController里设置闭包:

override func viewDidLoad() {
    super.viewDidLoad()
    outlineView.getMenu = { [weak self] event in
        guard let self = self else { return nil }
        let clickedRow = self.outlineView.clickedRow
        if clickedRow >= 0 && /* 判断选中项类型 */ {
            return self.menuOne
        } else {
            return self.menuThree
        }
    }
}

更推荐的方案:用NSOutlineViewDelegate方法直接返回菜单

其实不用子类化,直接实现NSOutlineViewDelegate的outlineView(_:menuForItem:)方法就能搞定,完全符合Cocoa的设计习惯:

extension ViewController: NSOutlineViewDelegate {
    func outlineView(_ outlineView: NSOutlineView, menuForItem item: Any) -> NSMenu? {
        // 根据item的类型或状态返回对应菜单
        if let targetItem = item as? YourItemTypeA {
            return menuOne
        } else if let targetItem = item as? YourItemTypeB {
            return menuTwo
        } else {
            return menuThree
        }
    }
}

这种方式直接就能访问ViewController里的IBOutlet菜单,还不用额外子类化,最适合你的需求。


内容的提问来源于stack exchange,提问作者user10711707

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 20:26:18