Build a Custom Sheet in Cocoa

blog — Tags: , , , , , — anon @ 10/03 7:51 pm

What is a sheet? Well, Cocoa introduced several new GUI elements like drawers that aren’t used by too many applications right now. They are used nicely, however, in Mail to house your mailboxes and in OmniWeb to store your bookmarks.

One of the elements that are available to Mac developers are called “sheets”. In particular, sheets are those things that slide out of the title bar of a window. In fact, a sheet is just a NSPanel that is being attached to the application window at runtime. You can see a sheet in action when actually opening or saving a file.

What is the difference between a sheet that slides out and a modal window that pops up? Well, there are some serious considerations. You may also want to check out this tutorial by Erik. J. Barzeski on CocoaDevCentral which I used as a informational and textual base for this post. There you can find a little bit more about modal windows which I don’t want to explain right here in depth.

In short, sheets are a very stylish way to let the user know that a certain action needs to be done or that there is something wrong and that need to be fixed.

A Simple Example

Create a new Cocoa application project in XCode. Choose the Classes folder from the “Groups & Files” list on the left and add a new Cocoa class. Rename the file to “SheetController.m” and check the box right below where you type in the file name so XCode will generate both, the .h and .m file.

For a simple example application you don’t need to worry about any actions or outlets for any of the various controls. Instead, only two outlets are needed – one to the sheet and one to the main application window. Additionally add two actions – one for the window to call the sheet and one for the sheet to close.

Edit your header file to look like this:

#import <Cocoa/Cocoa.h>

@interface SheetController : NSWindowController
{
   IBOutlet id mainWindow;
   IBOutlet id panel;
}

- (IBAction)add:(id)sender;
- (IBAction)complete:(id)sender;
- (IBAction)cancelOperation:(id)sender;

@end


If you like to work from within Interface Builder you can simply subclass NSWindowController and add the appropriate outlets and actions and generate the files.

Otherwise, double-click MainMenu.xib in your project’s Resources folder to launch Interface Builder and drag your “SheetController.h” file into the MainMenu.xib window. Interface Builder will then parse the header and automatically add the needed object.

Sheets are of type NSPanel typically. Create a new panel and set it’s attributes like this:

Interface Builder 3 NSPanel Attributes (Small)

Give the panel a Title and set the attributes to your liking. You can safely disable close, resize and minimize as you don’t need them anyway. If you want the panel to be textured you should enable this option as well as leaving the shadow option enabled.

Be sure that you disable “visible at launch”, otherwise the panel would be visible when the application start and that not what we want here.

Sheets can be of type NSWindow, too. You would, in any case, set the same settings for the window as for the panel. In this example everything was prepared using Interface Builder but you could also create everything from code.

Go ahead and add some controls to the panel and be sure to add a button for closing the sheet again – typically named “Done”.

Next we’ll connect the outlets and actions and finally write the code to get the sheet running fine.

Connect (control-drag) from each of the buttons to your controller and attach them to the correct action. Likewise, control-drag from your controller to each of the windows (either in the MainMenu.xib window or to the actual on-screen layout representation).

Save your MainMenu.xib file and quit Interface Builder. It’s time to write some code.

Edit your code file to look like this:

#import "SheetController.h"

@implementation SheetController

- (IBAction)add:(id)sender
{
    [NSApp beginSheet:panel modalForWindow:mainWindow modalDelegate:self
    didEndSelector:NULL contextInfo:nil];
}

- (IBAction)complete:(id)sender
{
    [panel orderOut:nil];
    [NSApp endSheet:panel];
}

- (IBAction)cancelOperation:(id)sender
{
    [panel orderOut:nil];
    [NSApp endSheet:panel];
}

@end


So this is how a sheet would basically look like. If you’ve never seen NSApp before, it’s essentially a pointer to your application object. The next two portions of the method, beginSheet: and modalForWindow: tell your application which window/panel is the sheet, and which window to which it should attach the sheet.

I haven’t defined a delegate, so the sheet can act as its own delegate. The didEndSelector: and contextInfo: pieces are not necessary for simple sheets, so we pass them NULL and nil.

To get rid of the sheet, tell it to orderOut: (go away) and then tell the application to endSheet: – note that this is like a bookend to beginSheet:. And that’s literally all we’ve got to do. Of course, if we wanted to read in the changes made in the sheet, we’d need outlets to all of the controls, buttons, text fields, etc.

That’s it! Save, Build, Run. Click the button. Watch the sheet appear.


1 Comment »

  1. Awesome! Thanks…

    Had a hard time finding many examples for this, nice post :)

    Comment by Justin — 10/04 @ 10:23 pm

RSS feed for comments on this post. TrackBack URI

Leave a comment

copyright © 2008-2010 datenkompost.de/blog - barecity derivative | imprint
* a title remix inspired by a popular german book