Cocoa Programming for Mac OS X: Chapter 12 Challenge
Pesky!

This is another challenge that is easy but can trip you up when you test it… The challenge reads:
Create a nib file with a custom About panel. Add an outlet to
AppControllerto point to the new window. Also add ashowAboutPanel:method. Load the nib by usingNSBundle, and makeAppControllerFile’s Owner.
The easy part of this is that you don’t have to create a subclass like you did with PreferenceController. Read the bits about NSBundle and you’ll see that all you have to do is to modify the File’s Owner (AppController) and create the nib/xib file. That’s simple enough.
I initially didn’t understand the purpose of creating an IBOutlet for the About panel. After testing the application, I realized that the implementation of showAboutPanel: allowed for multiple panels to be opened.
Pesky! Read the rest of the post to see the code and instructions.
To do this properly:
- Create the About.xib file in the same manner as done with the preferences file during the chapter. Make sure that you set the class of File’s Owner to be
AppController. You don’t need any of the color wells or check boxes. You do, however, need to change one of the check boxes. Select the Panel object in Interface Builder and then UNcheck the “Release When Closed” check box in the Behavior section of the Attributes inspector. If you do not uncheck this, then theIBOutletthat you declare in AppController.h is released and you can only open the About panel one time without the program crashing. - Follow the directions and declare
IBOutlet NSPanel aboutPanel;in AppController.h. This is part of what I didn’t understand at first, since the bit in the book aboutNSBundlepoints out that you can load the nib file withoutNSWindowController. - Declare
-(IBAction)showAboutPanel:(id)sender;in AppController.h. Your AppController.h file should look like this:
#import <Cocoa/Cocoa.h>
@class PreferenceController;
@interface AppController : NSObject {
PreferenceController *preferenceController;
IBOutlet NSPanel *aboutPanel;
}
- (IBAction)showPreferencePanel:(id)sender;
- (IBAction)showAboutPanel:(id)sender;
@end
- Open MainMenu.xib in Interface Builder and connect the About menu item to App Controller. Control-click on AppController and drag from the received actions for
showAboutPanel:to the About menu item and release. Follow the same procedure that you did for the preference panel earlier in the chapter. The method show below is, incidentally, the same as control dragging from the About RaiseMan menu item and releasing it over App Controller, then selectingshowAboutPanel:from the Received Actions popup.

- Return to About.xib in Interface Builder and control-click on File’s Owner. You need to tell File’s Owner (
AppController) what yourIBOutletcalledaboutPanelpoints to, so drag from the little circle to the Panel icon in the same window, like this:

- Ok, now you need to write the
-(IBAction)showAboutPanel:method in AppController.m. However, keep in mind that you want to check to see whether theaboutPanelalready is open. Create a simple if statement. This was the pesky part and is one of the reasons for creating theIBOutlet. The logic is: if theaboutPaneldoes not exist, create it and bring it to the front. If it does exist, bring it to the front. The code for AppController.m looks like this:
#import "AppController.h"
#import "PreferenceController.h"
@implementation AppController
- (IBAction)showPreferencePanel:(id)sender
{
// Is preferenceController nil?
if (!preferenceController) {
preferenceController = [[PreferenceController alloc] init];
}
NSLog(@"showing %@", preferenceController);
[preferenceController showWindow:self];
}
- (IBAction)showAboutPanel:(id)sender
{
if (!aboutPanel) {
BOOL successful = [NSBundle loadNibNamed:@"About" owner:self];
// This makes the about panel the key window - gives it focus
[aboutPanel makeKeyAndOrderFront:nil];
NSLog(@"About panel loaded with success status %d.", successful);
return;
}
// The About panel is open, so just give it focus.
[aboutPanel makeKeyAndOrderFront:nil];
NSLog(@"About panel given focus.");
}
@end
Now, assuming that you followed the steps and connected everything in Interface Builder, you should be set to test the application. Make sure that you can’t open multiple about panels.
On a larger scale, think about this from the point of view of using NSBundle to open the About.xib file. The text just above the Challenge in the book says that you could do it like this: BOOL successful = [NSBundle loadNibNamed:@"About" owner:someObject]; (In this instance, since this code exists within AppController and AppController is the File’s Owner, then someObject can be replaced with self.) This code is a ‘dumb’ opening of the nib file. It doesn’t know if the About panel already is open.
To prevent it from doing the same thing repeatedly, you have to insert the if statement to check whether the window already is open. Hence, the declaration of the IBOutlet NSPanel aboutPanel; It allows you to check for the existence of the About window.
“UNcheck the “Release When Closed” check box in the Behavior section of the Attributes inspector.” – Thx dude, I was staring at the code and reiterating your description of what to do several times until i noticed this was the cause of my crashes when reopening the About window!
Sven
September 30, 2010 at 16:36
thanks…
igooodman
January 17, 2011 at 02:34
I managed to complete this challenge by making the AppController to be the subclass of NSWindowController, and call showWindow of NSWindowController (due to the crash when calling NSPanel’s makeKeyAndFront)
Your solution is the true work!
Thanks!
ZZig
May 23, 2011 at 08:23
I spent a long time looking for this on the web. Thanks for the tutorial!
I offer up my initial search keywords: OSX NSPanel Popup Tutorial
Nate
September 27, 2011 at 12:09
I think you mean ‘yo mama’… ;-) Awesome walkthrough by the way
adamjansch
January 6, 2012 at 18:06
Everything works for me, except…
- When the panel is launched, it’s on top of the app’s window, but it doesn’t have focus…
- The panel doesn’t limit itself to one instance … if you click the “About” menu item again, it opens another, and another, and another…
- And, most annoyingly, every time you open the panel, it reloads the WebView I have in the app’s main window!
Any thoughts, or updates? This looks like it was done in Xcode pre-version 4, so all the IB stuff looks different, and I never used any of the earlier, non-integrated versions of Xcode/IB, so it’s a little confusing
Thanks for any help!
Matt
April 18, 2012 at 14:56
Well, it sure helps to not skip steps! The first two problem have been alleviated; the last one, however, is still a problem. It only happens the first time you open the panel (after closing it and opening it again, the WebView is not reloaded), which is that my WebView reloads. Any ideas?
Matt
April 18, 2012 at 15:11
It’s been a long time since I’ve looked at that (and unfortunately, I lost my Xcode projects in a hard drive crash). I suspect something is happening during the init method for the panel that isn’t being called again. If the panel is retained by another object after its creation, then the init method only would be called once. You might browse over to the Big Nerd Ranch forum where people are actively looking at those problems. :)
It could also be that the web view reloads when it is sent the message to make it key.
Clay
April 18, 2012 at 15:16
Never mind on the third one, too … you can just delete these comments. I added an if statement to my awakeFromNib: method that causes it to only execute if aboutPanel is not open, and because it executes immediately upon launch, it’s not an issue. Thanks for the great tutorial!
Matt
April 18, 2012 at 15:17
Glad you got it working!
Clay
April 18, 2012 at 15:19