When developing iOS Apps it’s very rare to come up against a technical challenge that hasn’t already been solved and the solution shared on Stack Overflow or in Apple’s sample code in the iOS Dev Center. Unfortunately I haven’t experienced the same luxury with Mac OS X Development.
In a Mac App that I’m currently developing for a client I need to be able to create modally presented windows as sheets as described here in Apple’s Human Interface Guidelines.
Document Modal Dialogs or “Sheets” are instances of NSWindow. According to Apple’s documentation to present a custom NSWindow sheet we use the following code:
modalForWindow: window
modalDelegate: self
didEndSelector: @selector(didEndSheet:returnCode:contextInfo:)
contextInfo: nil];
Problem is beginSheet:modalForWindow:modalDelegate:didEndSelector:contextInfo: was deprecated a few years ago and appears to no longer work in Yosemite and Mavericks 😕 With out-of-date documentation and all Googling resulting in out-of-date solutions to presenting custom modal windows, I went about figuring out the modern solution to this challenge!
The replacement for beginSheet:modalForWindow:modalDelegate:didEndSelector:contextInfo: is to now call beginSheet:completionHandler: on the NSWindow that you want to present from, passing in reference to the instance of your custom NSWindow subclass.
I also discovered a few important steps that you need to implement when presenting sheets…
Step 1: Retain a reference to the NSWindow or NSWindowController that you are creating when presenting a sheet:
@property (strong) CustomModalWindowController *myCustomModalWindowController;
@end
@implementation MainWindowViewController
- (IBAction)didTapOpenButton:(id)sender {
self.myCustomModalWindowController = [[CustomModalWindowController alloc] initWithWindowNibName:@"CustomModalWindowController"];
[self.window beginSheet:self.myCustomModalWindowController.window completionHandler:^(NSModalResponse returnCode) {
}];
}
@end
Step 2: Deselect the Visible at Launch window attribute for your custom NSWindow or it’ll fail to present modally
Step 3: Call the window “sheetParent” in order to close the sheet and trigger the sheet completion block in the presentation code:
I’ve created a demo Xcode project that you can grab from my github repository to see this all in action:
Download source example from Github
Happy Coding!
Wayne Cochran
Thanks Nick.
I see you are placing all your view components in a NIB file, but there is no way to add a bare NSWindow to a storyboard. I tried throwing in a NSWindowController into the storyboard and using a modal segue, but I can’t figure out how to get it to present as an action sheet (you’re right as to Stack Overflow not being much help).
Wayne Cochran
As I submitted the last comment it dawned on me to use an NSViewController — you can have the segue present as a “sheet” in that case. Actually easier now with storyboards.
Gordon Apple
Thanks for the comment about deselecting “visibleAtLaunch”. At least, it appears attached to the window now. However, I still don’t see a good way to make it send the return code without a controller.
Shaan van Inde
Superb! Thanks.
Please post this to StackOverflow where it will help others searching for an answer to the same problem.
Guillaume Laurent
Thanks for your post, it just helped me save time today. One thing though, you can do without the CustomWindowController, and instantiate the sheet directly from the ViewController.
For this to work, you first need to set the FileOwner in the CustomModalSheet.xib to CustomModalViewController, and then you can directly bind any actions or outlet from the xib to CustomModalViewController itself. You can then instantiate the sheet as follows :
NSArray* topLevelObjects;
[[NSBundle mainBundle] loadNibNamed:@”CustomModalSheet” owner:self topLevelObjects:&topLevelObjects]
with ‘self’ being your ViewController.
Martijn
Thank you for this interesting post. However, whatever I did, the custom Window was not shown at all, although ‘windowDidLoad’ was called on my custom NSWindowController.
When I enabled the option “visibleAtLaunch”, the window was shown modally, at a random position on the screen.
Finally, based on this post, I was able to show the custom Window as an embedded sheet:
https://mikeash.com/pyblog/friday-qa-2013-04-05-windows-and-window-controllers.html
The key was to connect in IB the controller’s window outlet to the “window” property on the File’s Owner.
Hope this might help others.
Maury Markowitz
Just like to say thanks to Wayne, who helped me implement his solution. I’d recommend his storyboard-based solution to anyone who’s happy working with >= OSX 10.10. Once he explained the basic concept, I was able to wire up all of my sheets in a few minutes. My only tip would be that if you want to trigger the segue into the sheet from a menu item, link the segue to the root window’s Files Owner icon (the blue box) and then link the menu item to First Responder. That will ensure the right NSDocument gets the action message, and it will fire the segue.
Colin Masters
Ha, thanks bro, you saved my skin with this one! I kind of feel like we should write a faux Stackoverflow Question/Answer so this blog can be referenced. Biggest annoyance was not really knowing what I was looking for!
Matthew Self
I am using a sheet to display a progress bar for a long operation. The sheet closes itself when the task is done. It works fine, except in the case that the user hides the app while the sheet is up. If the sheet closes itself while the app is hidden, then when the user unhides the app the sheet is still there (blank) and can’t be dismissed.
Any ideas?
Tom Corwine
Thank you so much for this!
David Hall
Having not written much Objective C for a while I’ve had a bit of (re)learning to do. And Apple have moved the goalposts yet again. Despite this post having been written in 2015, there is still virtually no clear information on the internet about how to display modal sheets in the new way – certainly the Apple documentation doesn’t help much. So very many thanks for this post!!