Question

I have a Document based application. I want to add a contextual menu that displays context-sensitive info when the user right-clicks selected text in an NSTextView.

I have followed the advice in the Apple documentation and

  • Added an NSMenu as a root object in my XIB file.
  • Connected the NSMenu instance to the menu outlet of the NSTextView.
  • Connected an IBAction to the NSMenuItem inside the NSMenu.

So far so good. Every thing works as expected: the menu item appears and the action is called when it is selected.

I need to get the selected text from the NSTextView before the menu appears so that I can configure my menu item appropriately. According to the docs

If you need to customize the contextual menu, you can do so by setting an appropriate object as the menu’s delegate and implementing the menuWillOpen: method to customize the menu as you see fit just before it appears.

I connect the delegate of the NSMenu to File's Owner. None of the delegate methods are called. ( menuWillOpen: is the only one I need, but I've tried others, too).

I set a breakpoint inside the IBAction that gets called when the menu item is selected. If I inspect the menu with the debugger I can see that the delegate is correctly set to the object that implements the delegate method.

Is there anything else to check? Anything I'm doing blatantly wrong?

Xcode v4.6.3
SDK v10.8
Deployment target 10.7

Was it helpful?

Solution

After some digging, this is what I found: NSTextView builds a different NSMenu instance to use as the contextual menu, probably by overriding -menuForEvent: or some similar internal method. This new menu copies the menu items from the menu you created in Interface Builder (in fact, it creates new menu item instances whose attributes are copied from the original menu items) but it does not copy the menu delegate, which is why your menu delegate does not receive -menuWillOpen:. I am not sure whether this is intentional or not. Reading that documentation quote you posted, it seems to be a bug.

What you can do is to set the delegate of your NSTextView instance to an object whose class conforms to NSTextViewDelegate (maybe your File’s Owner, which already conforms to NSMenuDelegate) and implement the following method:

- (NSMenu *)textView:(NSTextView *)view menu:(NSMenu *)menu forEvent:(NSEvent *)event atIndex:(NSUInteger)charIndex
{
    // if the menu delegate is not self, set another object
    [menu setDelegate:self];

    return menu;
}

This will make sure that the contextual menu created by the text view uses your delegate.

NB: since NSTextView creates a different contextual menu, it could be the case that it might want to set the menu delegate to itself or some other internal object. In my tests the delegate is nil, so it looks like it’s safe. Alternatively, you could discard the proposed menu argument and return your own NSMenu instance with the delegate correctly set.

OTHER TIPS

Finding this thread saved me a lot of time...thanks! Here's an implementation that works in an NSView in Swift. myNSMenu is an outlet from Storyboard to appDelegate and a subclass of NSMenu. Without the assignment of the delegate in the code below, the NSMenuDelegate functions were not called.

    let appDelegate = NSApplication.sharedApplication().delegate as! AppDelegate
    appDelegate.myNSMenu.delegate = appDelegate.myNSMenu
    NSMenu.popUpContextMenu(appDelegate.myNSMenu, withEvent: theEvent, forView: self)        
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top