Edit AllPages

Here’s a question I know has been asked and asked, but I can’t seem to find a good answer to. I want to create a multi-pane preferences window (like in Mail, Safari, or any other Apple app) that uses an NSToolbar to switch between the panes. From what I can tell by looking in the package, each pane is a separate Nib file, which comes together at runtime. I, however, have absolutely no idea where to begin implementing this. I know that you can use OmniAppKit framework to do it, but I just thought it is stupid to include the framework in an app that is only going to use one class, since OmniAppKit is bloated anyway. Anyone have a good idea how to do this? I’m kind of new to Cocoa (only a few months experience) so please bear with me! Thanks! –LoganCollins

Just for a starter, try looking at Apple’s MultipleNibTabView example (InterfaceBuilder section). Then try making the view tabless, turning off customization on the NSToolbar, and using selectable items to select the tabs. Just an idea. Also, if you want to rely on private APIs (bad idea, but it’s worth mentioning), check out NSPreferences. –JediKnil

I went through the same process you did. I started browsing CocoaDev and couldn’t find any help anywhere. I found some code online that promised to make this process easy, but it really just made it more complicated. All I did was go into my MainMenu.nib and make some views. Just drag and drop a “CustomView” from the palette onto your instances window and change the name from “View” to something like “General”. Then just put interface items on that and do whatever you want to it. Then in your controller, make an outlet that points to the view you just made (you’ll probably want outlets to point to all the interface items you made too. That is why a separate controller could be beneficial for organizational purposes.). Then it is just a matter of setting up an NSToolbar with items that point to some method like “changView:”. In changeView, you can just do some checking of the sender NSToolbarItem to switch to the appropriate view. Let me post some sample code.

So in my PreferencesController I have:

So that sets up all the toolbar stuff and also sets up the NSMutableDictionary that will hold all of our toolbar items. This is needed for the delegate methods. You need to have some kind of global variable to hold this information, so I made it a class variable for PreferencesController. Also, you need a class variable for the toolbar. Lets take a look at the delegate methods for the toolbar (remember, we set the delegate to be PreferencesController in our awakeFromNib.

//toolbar delegate methods.

Ok, so that sets up our toolbar. Now we need a way to switch between the views.

//called everytime a toolbar item is cilcked. If nil, return the default (“General”).

I’m almost positive I missed something or wasn’t clear on a few things. Feel free to post here with questions. Good luck! –[[ZacWhite

That code works perfectly! Many, many thanks for finally solving this problem. –LoganCollins

This was a big help! –NickGerakines

Here’s a class that will let you add a fully functional preferences window to your application with just a few lines of code (one line for each preference pane). It also adds a nice cross-fading feature (like Apple’s DVD Player preferences) when switching between panes.

There’s also a tutorial that will show you how to use this class to add a preferences window to your application, and wire it up to NSUserDefaults using CocoaBindings. It also shows you how to set and use these defaults from your code.

This code will not work out of the box if your sheets are of differing widths (not advisable in most situations but I guess it could come in handy at some point). You can change the frame arithmetic like so in order to keep center-alignment of your sheets: –DanGlegg

// Figure out destination frame NSRect newFrame = [myWindow frame]; newFrame.size.height = [prefsView frame].size.height + ([myWindow frame].size.height - myWindow contentView] frame].size.height); // Compensates for toolbar newFrame.size.width = [prefsView frame].size.width; // this is just the new view’s width, unadorned newFrame.origin.y += ([[myWindow contentView] frame].size.height - [prefsView frame].size.height); // Origin moves by difference in two views newFrame.origin.x += ([[myWindow contentView] frame].size.width - [prefsView frame].size.width)/2; // Origin moves by difference in two views, halved to keep center alignment

The various code snippets above to resize the window when the pane changes don’t work properly when the UI scale factor is not equal to one. Here’s what I use. Run Quartz Debug, bump up the UI Resolution, and give it a go.

float vdiff = ([prefsView frame].size.height - [[window contentView] frame].size.height) * [window userSpaceScaleFactor];
newFrame.origin.y -= vdiff;
newFrame.size.height += vdiff;
float hdiff = ([prefsView frame].size.width - [[window contentView] frame].size.width) * [window userSpaceScaleFactor];
newFrame.size.width += hdiff;

I came across a great article that covers many topics related to the OP’s question: –[[NateVW