2
votes

Everyone discussed about how to get rid of the blue outline during right click... but me.

Instead, I'm trying to display the blue outline.

I didn't get any outline when I right clicked my outline view row. The menu appeared but the outline wasn't.

You can see that the blue outline is not visible in this picture below:

enter image description here



Below is what I'm trying to achieve.

Blue outline


Update

This is how I implemented the NSMenu. I subclassed the NSOutlineView and made a new protocol to override NSOutlineViewDelegate.

This idea was to make it simple by letting the NSOutlineView ask the NSMenu for each item, so we can implement different menu for each item. It works but the blue outline view doesn't show up during right click.

KRMenuOutlineView.swift

import Cocoa

@objc protocol KRMenuOutlineViewDelegate: NSOutlineViewDelegate {
    // This method will ask NSMenu for each item in outline view
    func outlineView(_ outlineView: KRMenuOutlineView, menuFor item: Any, event: NSEvent) -> NSMenu?
}

class KRMenuOutlineView: NSOutlineView {

    override var delegate: NSOutlineViewDelegate? {
        didSet {
            if let newValue = delegate {
                /*
                 * Swift doesn't support overriding inherited properties with different type
                 * like Objective C Does, therefore we need internal delegate.
                 */
                internalDelegate = unsafeBitCast(newValue, to: KRMenuOutlineViewDelegate.self)
            } else {
                internalDelegate = nil
            }
        }
    }

    private var internalDelegate: KRMenuOutlineViewDelegate?

    override func menu(for event: NSEvent) -> NSMenu? {
        let point = self.convert(event.locationInWindow, from: nil)
        if let item = self.item(atRow: self.row(at: point)) {
            return self.internalDelegate?.outlineView(self, menuFor: item, event: event)
        }
        return super.menu(for: event)
    }
}

Then, I use it in my view controller like this:

KRTreeViewController.swift

extension KRTreeViewController: KRMenuOutlineViewDelegate {
    func outlineView(_ outlineView: KRMenuOutlineView, menuFor item: Any, event: NSEvent) -> NSMenu? {
            let menu = NSMenu(title: "Contextual Menu")
            menu.delegate = self

            let key = String(utf16CodeUnits: [unichar(NSBackspaceCharacter)], count: 1) as String
            let deleteMenuItem = menu.addItem(withTitle: "Delete",
                                              action: #selector(didClickMenuItem(_:)),
                                              keyEquivalent: key)
            deleteMenuItem.representedObject = myItem
            deleteMenuItem.target = self

            return menu
    }

    @objc fileprivate func didClickMenuItem(_ menuItem: NSMenuItem) {
        // ...
    }
}
1
We don't know what you did to make the blue outline dissappear. How did you implement the context menu? Did you do anything to alter any default behaviour of NSOutlineView?Willeke
@Willeke I updated my question.Edward Anthony
Try calling super.menu(for: event) before returning your own menu.Willeke

1 Answers

1
votes

How to properly show a context menu:

If you have created your menu using a storyboard: First, go to the storyboard and add the menu to the viewController that contains the outlineView. Then make it an @IBOutlet so you can reference it later.

In a method like viewDidLoad(), add the menu to the outlineView by calling

outlineView.menu = myMenu

where myMenu can either be the one you created in Interface Builder or in code.

You can run the app now and should see the blue outline around the cell.

The problem now is that you don't know which cell the user has clicked. To fix this, set yourself as the delegate of myMenu and adopt the NSMenuDelegate protocol.

func menuNeedsUpdate(_ menu: NSMenu) {
    let row = self.outlineView.clickedRow

    guard row != -1 else { return }
    for item in menu.items {
        item.representedObject = row
    }
}

Here you can do whatever you need. This implementation sets the rowIndex as the representedObject of each menu item. Keep in mind that this only works on static outlineViews (ones that don't change in the background) and menus which only go one level deep.

You could also store the index or object represented by the cell (if the outlineView is not static) in a local variable.