Search code examples
macoscocoansmenuitemnsresponder

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


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?


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.