8

I'm building a Mac app that only sits in the menu bar with no dock item and no key window and no main menu (it's LSUIElement in the info.plist is set to YES). When I first launch the app, applicationDidBecomeActive: is called, as I expect. However, once another app gains focus, applicationDidBecomeActive: is never called again.

This prevents a text field I have within my app from becoming the first responder. When I first open the app, the text field is editable:

before another app gains focus

But after another app comes to the foreground, the text field is not editable:

after another app gains focus

What I've tried:

When the menu is opened, menuWillOpen: is called on the NSMenu's delegate. I've tried placing the following with no success:

[NSApp unhide];
[NSApp arrangeInFront:self];
[NSApp activateIgnoringOtherApps:YES];
[NSApp requestUserAttention:NSCriticalRequest];
[[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
[[NSRunningApplication currentApplication] unhide];

I think the issue is probably related to not having any windows to bring to the front. I feel like I'm grasping at straws here. Any help would be greatly appreciated.

Ash Furrow
  • 12,391
  • 3
  • 57
  • 92
  • Why should it ever be called again if the app is never relaunched. If it's in the menubar, doesn't it's process stick around as a sort of quasi-daemon? – CodaFi Feb 28 '13 at 03:46
  • Perhaps it isn't in any way related, but I had a recent issue with iOS not making a view the first responder after a resume event (when it was the first responder prior to the resume). The fix was to resignFirstResponder before sending a becomeFirstResponder message. No idea why, but it got iOS's butt in gear. Perhaps something similar is in play here? – Kerri Shotts Feb 28 '13 at 04:15
  • Hey Ash, the problem may relate to NSMenu. Using it for text input has never been a good idea. Have you tried the same steps with NSWindow? – Vadim Feb 28 '13 at 08:16

3 Answers3

3

I think the issue is with that how the runloop operates when a NSMenu is open, so you should try activating the app before you display the menu. If you're having the NSStatusItem display it, I'd suggest doing it yourself like this:

- (void)toggleMenu:(id)sender
{
  // App might already be active
  if ([NSApp isActive]) {
    [self.statusItem popUpStatusItemMenu:self.menu];
  } else {
    [NSApp activateIgnoringOtherApps:YES];
  }
}

- (void)applicationDidBecomeActive:(NSNotification *)notification
{
  [self.statusItem popUpStatusItemMenu:self.menu];
}

That should work, but I think though in general you'll have better luck with an actual window instead of a menu.

Zach Waugh
  • 736
  • 1
  • 6
  • 9
1

You probably need to allow your input to -becomeFirstResponder, maybe by overriding -canBecomeFirstResponder or by calling the become method yourself.

You'd likely have to implement/call these methods for whatever view is housing your text input, or maybe tell your input view to become the first responder.

Either way, it smells like a responder chain issue.

jbrennan
  • 11,943
  • 14
  • 73
  • 115
1

Try calling -makeFirstResponder: on your window. NSWindow is usually the start of the NSResponder chain.

- (void)menuWillOpen:(NSMenu *)menu {
    [[NSApp mainWindow] makeFirstResponder:yourTextInputField];
}

I'm assuming your text field already accepts first responder since you said your app launches initially with it as the first responder. If not, make sure your text field overrides -acceptsFirstResponder: to return YES

- (BOOL)acceptsFirstResponder {
    return YES;
}

Edit: Ah, see that you don't have a key window. It looks like NSMenu actually has a window associated with it though, and it's safe to call -makeFirstResponder:. Some discussion here suggests overriding -viewDidMoveToWindow: on your view containing your text field in the NSMenu like so:

- (void)viewDidMoveToWindow {
    [super viewDidMoveToWindow];

    [[self window] makeFirstResponder:yourTextInputField];
}
slottermoser
  • 154
  • 1
  • 5