如何基于选择切换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
相关产品推荐
相关产品推荐

