Search code examples
objective-ccocoansmenu

Application main NSMenu not responding without window reactivation


I'm trying a minimalistic Cocoa app as described in this page using code:

#import <Cocoa/Cocoa.h>;
int main ()
{
    [NSAutoreleasePool new];
    [NSApplication sharedApplication];
    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
    id menubar = [[NSMenu new] autorelease];
    id appMenuItem = [[NSMenuItem new] autorelease];
    [menubar addItem:appMenuItem];
    [NSApp setMainMenu:menubar];
    id appMenu = [[NSMenu new] autorelease];
    id appName = [[NSProcessInfo processInfo] processName];
    id quitTitle = [@"Quit " stringByAppendingString:appName];
    id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle
        action:@selector(terminate:) keyEquivalent:@"q"] autorelease];
    [appMenu addItem:quitMenuItem];
    [appMenuItem setSubmenu:appMenu];
    id window = [[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 200, 200)
        styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]
            autorelease];
    [window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
    [window setTitle:appName];
    [window makeKeyAndOrderFront:nil];
    [NSApp activateIgnoringOtherApps:YES];
    [NSApp run];
    return 0;
}

My issue is, that the application menu is not responding until I reactivate its window (activating another app and then clicking back to my app window).

Any idea why and how to resolve it?


Solution

  • The AppKit is still in your app's startup phase when you're attempting to call [NSApp activateIgnoringOtherApps:YES];. The correct way to do it is to handle it through NSApplicationDelegate:

    @interface AppDelegate : NSObject <NSApplicationDelegate>
    @property (strong, nonatomic, class, readonly) AppDelegate* sharedInstance;
    @end
    @implementation AppDelegate
    + (instancetype)sharedInstance {
        static AppDelegate *sharedInstance = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedInstance = [AppDelegate new];
        });
        return sharedInstance;
    }
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
        [NSApp activateIgnoringOtherApps:YES];
    }
    @end
    
    int main ()
    {
        [NSAutoreleasePool new];
        [NSApplication sharedApplication];
        NSApplication.sharedApplication.delegate = [AppDelegate sharedInstance];
        [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
        id menubar = [[NSMenu new] autorelease];
        id appMenuItem = [[NSMenuItem new] autorelease];
        [menubar addItem:appMenuItem];
        [NSApp setMainMenu:menubar];
        id appMenu = [[NSMenu new] autorelease];
        id appName = [[NSProcessInfo processInfo] processName];
        id quitTitle = [@"Quit " stringByAppendingString:appName];
        id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle
            action:@selector(terminate:) keyEquivalent:@"q"] autorelease];
        [appMenu addItem:quitMenuItem];
        [appMenuItem setSubmenu:appMenu];
        id window = [[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 200, 200)
            styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]
                autorelease];
        [window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
        [window setTitle:appName];
        [window makeKeyAndOrderFront:nil];
        [NSApp run];
        return 0;
    } 
    

    As a bonus the delegate's - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { will get invoked before your:

    dispatch_async(dispatch_get_main_queue(), ^{
        [NSApp activateIgnoringOtherApps:YES];
    });
    

    hinting it's the correct way to do it.