How do I route menu actions to an NSViewController inside a window?

cocoa, macos, nsmenuitem, nsresponder

Solution

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.

Problem

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?

Original source