Cocoa: Finding the window that your sheet is attached to

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!