Tips on NSApp’s ModalForWindow, NSAlert’s ModalForWindow, and ModalSession

7.2k views Asked by At

It took me quite a bit of experimentation to clear up some confusion over Objective-C’s “ModalForWindow” language and, subsequently, how to use a modal session. Maybe the following tips will save somebody some time:

(In case you’re new to the concept: When a window, usually a panel, runs modal, it prevents some other part of the app from responding until it has been dismissed.)

“ModalForWindow” means different things in different circumstances. If you are using loadNibNamed to display a panel defined by a xib and you want it to run modal, call this once it is displayed:

// Make panelReviewImports modal, so that no other part of app will respond.
[[NSApplication sharedApplication] runModalForWindow:self.panelReviewImports];

and follow up with this in its dismissal methods:

[[NSApplication sharedApplication] stopModal];

But for NSAlert, the “window” in beginSheetModalForWindow refers to the window to which the alert will be attached as a sheet, which window will be frozen until the alert is dismissed. But the app won’t be frozen; all other windows will remain operable. If you want to attach an alert as a sheet and also freeze the rest of the app, follow the beginSheet code with a simple call to runModal and use the return code explicitly, like this:

[alert beginSheetModalForWindow:self.window 
                  modalDelegate:self didEndSelector:@selector(abandonmentAlertDidEnd:returnCode:contextInfo:) 
                    contextInfo:nil];
NSInteger returnCode = [alert runModal];
[self abandonmentAlertDidEnd:alert returnCode:returnCode contextInfo:nil];

(Of course, you will have implemented the abandonmentAlertDidEnd:returnCode:contextInfo: code as a class method.)

Or, if you want the alert to run as a centered panel, call runModal by itself.

Suppose you want to run a panel modal, followed by an alert if the user submits an invalid entry. You’d have to stopModal before you show the alert — after which, for some reason, another call to runModalForWindow fails to work properly. For this scenario, you need a modal session:

1) Add an NSModalSession property to your controller class, because the modalSession must be accessible across multiple methods.

2) Once you have displayed the panel, call beginModalSessionForWindow to instantiate the modalSession:

self.modalSession = [[NSApplication sharedApplication] beginModalSessionForWindow:self.panelForInput];

3) Follow this up with a while-loop that calls runModalSession, breaking when its return does not equal NSRunContinuesResponse:

while ([[NSApplication sharedApplication] runModalSession:self.modalSession] == NSRunContinuesResponse)
    continue;

The loop will break and the app will free up when the user clicks on one of the panel’s buttons. (Typing in the panel’s textfield will leave the modal session intact.)

4) In your button handling, if the user’s entry is invalid, you call an alert with runModal.

5) Immediately below the alert call, in the code which will execute once the alert is dismissed, you put the same while-loop used above. The panel’s modal session resumes.

6) In your handling to close the panel, either upon valid entry or cancel, you call endModalSession, which, oddly, isn’t enough; you must also call stopModal, even though you never called runModalForWindow.

[[NSApplication sharedApplication] endModalSession:self.modalSession];
[[NSApplication sharedApplication] stopModal];
[self.panelForInput close];
1

There are 1 answers

0
Wienke On BEST ANSWER

The question is the answer. I'm just posting this to close it out. Sorry for twisting the stackoverflow format.