App stuck on runModalForWindow - objective-c

I'm trying to display a modal dialog on top of my app but it's blocking my main app window when it closes. Here's my code:
TutorialWindowController* pTutorialController = [[TutorialWindowController alloc] initWithWindowNibName:#"TutorialWindow"];
NSWindow* pTutorialWindow = [pTutorialController window];
DDLogInfo(#"Tutorial window opening...");
[NSApp runModalForWindow: pTutorialWindow];
DDLogInfo(#"Tutorial window closed!"); // CODE NEVER GETS HERE
[NSApp endSheet: pTutorialWindow];
[pTutorialWindow orderOut: self];
In the modal dialog, my Close button runs this:
- (IBAction)closeButtonPressed:(id)sender {
[NSApp stopModal];
}
The modal dialog displays fine. However, when I click the Close button, the dialog disappears and my app's main window isn't responsive. I hear the bonk every time I try clicking. I'm pretty sure this is because the code never continues after runModalForWindow. Same thing happens if I close the modal dialo using the red X.
What am I doing wrong?

After ordering out the tutorial window, try doing a
[window makeKeyAndOrderFront:self];
on your main window.

You should call [pTutorialWindow orderOut:nil] first.

Not sure about the closeButtonPressed handler. But try adding to the delegate:
- (void) windowWillClose:(NSNotification *)notification
{
// ...
// In there, you should verify that you are calling:
[NSApp stopModal]
}
Adding the stopModal call solved the issue for me.

Verify that the Window delegate in the Interface Editor's Connection Inspector is connected to the File's Owner.
I had several modal dialogues working correctly except for one, and the missing connection was the only difference. Making the connection fixed the problem.

Related

Opening a modal dialog (modal window or sheet) from another modal dialog

I need to open a modal window, which can open another modal window (or sheet). First window works fine, but second behaves strange.
This is how I open first window:
RegisterDialog * registerDialog=[[RegisterDialog alloc] initWithWindowNibName:#"RegisterDialogMac"];
NSWindow* window = [registerDialog window];
[NSApp runModalForWindow:window];
This window behaves properly and responds to any buttons, I just added a bit of code to stop modal event loop after pressing on a red button:
- (void)windowWillClose:(NSNotification *)notification {
[[NSApplication sharedApplication] stopModal];
}
Ok. Now I am opening the second dialog window if user press the "Register" button:
-(IBAction)registerPressed:(id)sender {
RegistrationDialogMac* registrationDialog=[[RegistrationDialogMac alloc] initWithWindowNibName:#"RegistrationDialogMac"];
NSWindow* window = [registrationDialog window];
[NSApp runModalForWindow:window];
}
This second dialog window works fine too, but is closed only if to press red button.
This is how "Cancel" button is processed:
-(IBAction)cancelPressed:(id)sender {
[NSApp stopModal];
[[self window] close];
}
After pressing it modal event loop is stopped, first dialog window becomes active, but second window still remains here. It is closed only if I press the red button. This is strange, as the first modal window is closed properly by the same code.
Ok. I tried to go different route and run the second dialog window as a sheet:
-(IBAction)registerPressed:(id)sender {
RegistrationDialogMac* registrationDialog=[[RegistrationDialogMac alloc] initWithWindowNibName:#"RegistrationDialogMac"];
NSWindow* window = [registrationDialog window];
[self.window beginSheet: window
completionHandler:^(NSModalResponse returnCode) { }
];
}
(I turned off "Visible at launch" to correctly draw this sheet attached to the first dialog window)
This way the second dialog window is drawn, but ignores all button presses. Graphically buttons are pressed, but aren't processed. This code:
-(IBAction)cancelPressed:(id)sender {
NSLog(#"!");
[[self window] close];
//[NSApp endSheet:[self window]]; // may be this would be correct way to close sheet? Don't know as this method isn't run anyway
}
isn't executed at all, there is no a single "!" character in the log.
So I am doing something wrong. I actually almost have no any Mac coding experience, only Windows.
Found that [self window] of the second dialog was nil, so it couldn't respond to close. Checked the .xib - and, yes, I forgot to connect the window outlet to the window itself. Fixed, and the window is closed correctly now.
This doesn't fix the problem of unresponding buttons when this second dialog is drawn as Sheet though.

Menu Bar App Never Becomes Reactivated

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:
But after another app comes to the foreground, the text field is not editable:
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.
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.
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.
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];
}

Show Window without activating (keep application below it active)

I need to show a window (without title bar) above third party applications without my window taking focus.
I have tried using an NSPanel and setting enabling non-activating, but that didn't help.
I tried orderFront:self, but that didn't help either.
I always needed to add [NSApp activateIgnoringOtherApps:YES]; because the window wouldn't show otherwise.
I have here a sample project for just this functionality:
http://users.telenet.be/prullen/TopW2.zip
UIElement is set to true in the application's plist file, so there is no dock. You can activate the window by pressing ALT + SPACE at the same time. You will see that the app below it looses focus. Any thoughts on how to fix this? I've seen other apps do it so I know it's possible.
Edit: here's the code so far. Remember the window is a non-activating NSPanel.
I still need that last NSApp activateIgnoringOtherApps line or otherwise it doesn't display. But of course that makes the window the active one.
_windowController = [[MyWindowController alloc] initWithWindowNibName:#"MyWindowController"];
[[_windowController window] setLevel:NSNormalWindowLevel+1];
[[_windowController window] orderFrontRegardless];
[_windowController showWindow:self];
[NSApp activateIgnoringOtherApps:YES];
I've also subclassed NSPanel and added two methods:
- (BOOL)canBecomeKeyWindow
{
return YES;
}
- (BOOL)canBecomeMainWindow
{
return YES;
}
Edit: OK, unchecking setHidesOnDeactivate fixes this, but now the window will never hide. I need it to hide when the user presses the app below it or switches to another app.
Edit 2: OK, this seems to fix the above issue:
- (void)awakeFromNib
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(hideWindow) name:NSWindowDidResignKeyNotification object:nil];
}
- (void)hideWindow {
[self setHidesOnDeactivate:YES];
}
Not sure if there's a better way.
And for those that want to know how to display the window:
[[_windowController window] setLevel:NSPopUpMenuWindowLevel];
[[_windowController window] orderFrontRegardless];
[[_windowController window] makeKeyWindow];
[_windowController showWindow:self];
Either one of these should do the trick:
Use -[NSWindow orderFrontRegardless] to get a normal level window to the front without activating the corresponding app, or
Use -[NSWindow setLevel:] to increase the window level to something higher than NSNormalWindowLevel
Not to take away from #puzzle's useful answer, but it sounds like your problem has something to do with using an NSPanel instead of an NSWindow.
The "How Panels Work" docs say:
Onscreen panels, except for alert dialogs, are removed from the screen when the application isn’t active and are restored when the application again becomes active. This reduces screen clutter.
Specifically, the NSWindow implementation of the hidesOnDeactivate method returns NO, but the NSPanel implementation of the same method returns YES.
So perhaps you could override hidesOnDeactivate to return NO, or change to NSWindow

Opening a new window and waiting for it to close

I have a Mac OS X app written in objetive-c Cocoa. You can see most of the code in this previous question. Essentially you click a button on the main window (the app delegate) and it opens another window where the user can enter information.
In the following code (that gets called when the user press the button in the app's main window)
- (IBAction)OnLaunch:(id)sender {
MyClass *controllerWindow = [[MyClass alloc] initWithWindowNibName:#"pop"];
[controllerWindow showWindow:self];
NSLog(#"this is a log line");
}
The NSLog line gets printer immediately after I called showWindow. Is there any way to wait until controllerWindow is closed to continue with the NSlog?
The reason for this is that the user set's a value on the new window I opened and I need to collect that value on the same OnLaunch so I need to wait.
I know that modal windows are bad form in Mac, but I have no control over this feature.
I've tried with
[NSApp runModalForWindow:[controllerWindow window]];
and then setting the popup window to
[[NSApplication sharedApplication] runModalForWindow:popupwin];
and it works but then the focus never gets passed to the main window anymore
Thanks!
If you want the window to be modal for your application, use a sheet: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Sheets/Tasks/UsingCustomSheets.html
However, there is no way to suspend execution of a method while the sheet is displayed, this would be tantamount to blocking the current run loop. You would have to break you code into the begin and end methods as described in the linked documentation.
Here are the steps you need to follow:
In TestAppAppDelegate create an NSWindow outlet to hold your sheet and an action to dismiss the sheet
Create a nib with an NSWindow as the root object. I think you already have this in "pop". Set the Visible at Launch option to NO (this is very important)
Set the file's owner of this nib to TestAppAppDelegate and connect the window to your new outlet, and the close button to your new action
In your method to launch the sheet (OnLaunch), use the following code:
(ignore this it's to make the code format properly!)
if(!self.sheet)
[NSBundle loadNibNamed:#"Sheet" owner:self];
[NSApp beginSheet:self.sheet
modalForWindow:self.window
modalDelegate:self
didEndSelector:#selector(didEndSheet:returnCode:contextInfo:)
contextInfo:nil];
Your close button action should be [NSApp endSheet:self.sheet];
Your didEndSheet: method should be [self.sheet orderOut:self];
You can use UIVIew method animateWithDuration:delay:options:animations:completion: to accomplish this.
You said you want the next line to execute once the window is closed, rather than after it is opened. In any case, you may end the OnLaunch method this way:
- (IBAction)OnLaunch:(id)sender {
MyClass *controllerWindow = [[MyClass alloc] initWithWindowNibName:#"pop"];
[controllerWindow animateWithDuration:someDelay:options: someUIAnimationOption
animations:^{
[controllerWindow showWindow:self]; // now you can animate it in the showWindow method
}
completion:^{
[self windowDidFinishShowing]; // or [self windowDidFinishDisappearing]
}
}
- (void) windowDidFinishShowing {
NSLog(#"this is a log line");
}

NSWindow Modal Problems

I'm running a modal window in my application, which works fine. The problem however seems to be dismissing the window.
While the window closes, it takes at least 20 seconds for the application to continue (I show the modal window at startup), but it will continue instantly if I click on its dock icon.
Here's how I close the modal window:
[NSApp stopModal];
[updaterWindow orderOut:self];
[updaterWindow close];
I've tried various things to get the Application to continue faster, such as manually switching to the main run loop and speaking to the main window directly (asking it to become the key window), which all did not work. Same as before, 20 seconds wait or instant load when clicking on the dock icon.
What can cause something like this? I'm really baffled.
Edit:
My modal window is actually created in a really simple way. I run my window as modal:
[NSApp runModalForWindow:updaterWindow];
When I am done with things, I close it:
[NSApp stopModal];
[updaterWindow orderOut:self];
[updaterWindow close];
The window goes away, but it needs a click to the dock icon or anywhere on the screen for the app to continue.
I'm doing something similar in my app at the moment: I have an IBAction method that calls the modal window:
-(IBAction)showMyModalWindow:(id)sender {
[theModalWindow makeKeyWindow];
NSInteger retVal = [NSApp runModalForWindow:theModalWindow];
[theModalWindow close];
//continue and do somethin according the value in retVal
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}
In this case theModalWindow is in it's own nib file has an NSObject in it that deals with the ok and cancel button to dismiss theWindow, by way of example here's the cancel button callback:
-(IBAction)cancelButton:(id)sender
{
[NSApp stopModalWithCode:errAuthorizationCanceled];
}
The last line in showMyModalWindow: method seems to be what's needed in my case by sending the app forward... Hope that's of some use!
Todd.