9

I have a menu item inside the main app menu and I’d like to route its action to a view controller (NSViewController). The interface hierarchy looks like this: There’s a main app window controller by an NSWindowController. Inside the window there’s a split view, and the right view in the split view is controlled by the NSViewController.

Window + NSWindowController
    `-- NSSplitView
           `-- NSView
           `-- NSView + NSViewController

The menu item is connected to First Responder in the Interface Builder. The view controller in question implements the appropriate method, but the menu item stays disabled. When I move the method to the NSWindowController, the menu item gets enabled.

I figured I need to get the view controller to the responder chain, so I set it as the nextResponder for the window controller; no cigar. What am I doing wrong?

zoul
  • 102,279
  • 44
  • 260
  • 354
  • You can add an object in IB, representing your Controller. Then link the menu action to the IBAction of your controller. – Matthieu Riegler Jul 12 '12 at 14:17
  • Unfortunately that’s not possible, the view controllers change according to what’s selected in the left split view pane. – zoul Jul 12 '12 at 14:19
  • Then going to have to reasign the menu action each time the view get's focus. To access te menu : [[[[[NSApp mainMenu] itemWithTitle:@"ItemName"] menu] itemWithTitle@"ItemName] setAction:@"Selector(theSelector)] – Matthieu Riegler Jul 12 '12 at 14:25
  • 1
    Yes, that’s a possible workaround (thank you), but I’d like to get it working the official way through the responder chain, because reassigning the target for my menu items by hand sucks. – zoul Jul 12 '12 at 14:33

2 Answers2

3

In the end I added a base class for my window controllers and made it forward calls to the “child” controllers:

- (id) childControllerForSelector: (SEL) selector
{
    for (id controller in [childControllers copy])
        if ([controller respondsToSelector:selector])
            return controller;
    return nil;
}

- (BOOL) respondsToSelector: (SEL) selector
{
    return [super respondsToSelector:selector] ? YES :
        [self childControllerForSelector:selector] ? YES :
            NO;
}

- (void) forwardInvocation: (NSInvocation*) invocation
{
    id child = [self childControllerForSelector:[invocation selector]];
    [invocation invokeWithTarget:child];
}

- (NSMethodSignature*) methodSignatureForSelector: (SEL) selector
{
    NSMethodSignature *signature = [super methodSignatureForSelector:selector];
    if (!signature) {
        id child = [self childControllerForSelector:selector];
        signature = [child methodSignatureForSelector:selector];
    }
    return signature;
}

It’s a lot of code, but it’s a general solution that keeps the controller code free from ad-hoc forwarding. Hopefully it’s not too much magic.

zoul
  • 102,279
  • 44
  • 260
  • 354
2

You can set the window controller as the delegate of the window so it will now be a part of the responder chain.

Assuming that you have your own subclass of NSWindowController, you can then simply catch the menu event there and call your appropriate methods in your controllers.

Unfortunately, the docs advice against trying to insert anything in the responder chain between the various views and subviews, so you can't just squeeze your view controller in there.

More here, but I take it you have already consulted that.

Monolo
  • 18,205
  • 17
  • 69
  • 103