I recently had a situation where I needed to chain together two modal sheets off the same window. When the first sheet was presented, I obviously had the NSWindow
object for the window I wanted to attach to. Since my sheet had its own controller object, I could have simply stored this NSWindow
object and used it later for the next sheet.
However, I decided – largely as a matter of taste – that I’d rather figure out the sheet’s “parent” window programmatically in the didEndSelector
when the first sheet is dismissed. That way I don’t need to store the parent window anywhere in my controller.
To do so, you can walk the window list and ask each window for its attachedSheet
, comparing against the sheet window that is being dismissed. I’ve wrapped this in a category on NSWindow
, shown below.
/* NSWindow+BSSheetParent.h */ #import <Cocoa/Cocoa.h> @interface NSWindow (BSSheetParent) - (NSWindow*) sheetParent: (NSWindow*) sheetWindow; @end /* NSWindow+BSSheetParent.m */ #import "NSWindow+SheetParent.h" @implementation NSWindow (BSSheetParent) - (NSWindow*) sheetParent { NSWindow * sheetParent = nil; if(self.isSheet) { NSArray * allWindows = [NSApp windows]; for(NSWindow * aWindow in allWindows) { if(aWindow == self) continue; if(aWindow.attachedSheet == self) { sheetParent = aWindow; break; } } } return sheetParent; } @end
Lines of note:
- Line 20 – If the argument window isn’t a sheet, then don’t bother searching. Its sheet parent will be nil
- Line 21 – get all of the windows from the app controller
- Line 22 – iterate through all the windows
- Line 25 – if we find the argument window, just skip it. It can’t be the sheet parent of itself
- Line 28 – If the current window’s attached sheet is the argument window, then we’ve found the sheet’s parent so we’re done
Here’s how you might use it:
/* MyController.m */ @implementation MyController - (void) runMySheetAsModalForWindow: (NSWindow*) parentWindow { [NSApp beginSheet: self.window modalForWindow: aWindow modalDelegate: self didEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:) contextInfo: nil]; } - (void)sheetDidEnd: (NSWindow*)sheet returnCode: (NSInteger)returnCode contextInfo: (void *)contextInfo { BOOL needsToShowAlert = YES; // figure out whether to warn the user if(needsToShowAlert) { NSWindow * sheetParent = [sheet sheetParent]; NSAssert(sheetParent != nil, @"Sheet apparently isn't hosted by another window"); [sheet orderOut: self]; // chain sheet alert... NSBeginAlertSheet(@"Alert Title", nil, // default @"Cancel", nil, sheetParent, self, @selector(warningAlertDidEnd:returnCode:contextInfo:), NULL, NULL, @"Warning - do you really want to delete all your data?"); } } /*...*/ @end
Here’s what this does:
- Line 5 – This is the entry point for running my sheet. It would be called from another window controller with the intent of having attaching the sheet to its window. If I were going to cache the sheet’s host window, this method is where I’d store it in an instance variable.
- Line 7 – Run the sheet.
self.window
is in an instance variable set up when the controller is initialized by the Nib (not shown) - Line 14 – This is what is called when the sheet is dismissed. Code to dismiss the sheet is not shown for brevity (use
[NSApp endSheet: returnCode])
- Line 18 – You’d normally want to do something here to figure out whether to show another sheet (like a warning), like check the return code or check some values in your sheet controls.
- Line 22 – Figure out the sheet’s host window, and assert if it isn’t found (it should be!). This uses the category shown above to do the heavy lifting.
- Line 25 – Get rid of the current sheet
- Line 28 – Now show another sheet from the same originating window.
Pretty straightforward, but also pretty useful in this circumstance.
Enjoy!