Adding Custom Color Pickers to a Color Panel

A color picker is a user interface for color selection in an NSColorPanel object. The color panel lets the user select a color picker from a matrix of NSButtonCell objects across the top of the panel. A color picker is a loadable bundle with an extension of .colorPicker that can be installed in one of four places:

The bundle should contain all required resources for the color picker, including nib files, image files, and so on. These resources should be internationalized for each supported localization. NSColorPanel allocates and initializes an instance of each class for each color-picker bundle found in these locations. The class name is assumed to be the bundle directory name minus the .colorPicker extension. If you have an icon in TIFF format (including a .tiff extension) and with the same name as that of the color-picker class, the color panel displays it in the button cell at the top of the panel.

Overview of the Color Picker API

The NSColorPickingDefault and NSColorPickingCustom protocols provide an interface for adding custom color pickers to an application’s color panel. The NSColorPickingDefault protocol provides basic behavior for a color picker. The NSColorPickingCustom protocol provides implementation-specific behavior.

The NSColorPicker class implements the NSColorPickingDefault protocol. To implement your own color picker you must create a subclass of NSColorPicker and implement the NSColorPickingCustom protocol for that subclass. You can also re-implement any NSColorPickingDefault methods if there is a need to; for example, you could write code to supply a custom tool tip or an image with a name other than the color-picker class name. You must also implement a view containing the actual color-selection user interface to be inserted into the color panel for the color picker. The custom NSColorPicker object should have an outlet connecting it to this view.

The color-picker API requires that you specify supported color-picker modes. For a list of the existing color picker modes and mode constants, see Choosing the Color Pickers in a Color Panel. If your color picker includes submodes, you should define a unique value for each submode. As an example, the slider picker has four values defined in the above list (NSGrayModeColorPanel, NSRGBModeColorPanel, NSCMYKModeColorPanel, and NSHSBModeColorPanel)—one for each of its submodes.

The Procedure for Creating a Custom Color Picker

What follows is a short tutorial for creating custom color pickers that are automatically added to a color panel. The procedure is fairly easy, but there are a few details you should be aware of. The tutorial makes use of the RadiantPicker example project installed in /Developer/Examples/AppKit.

Start by creating a project for a Cocoa bundle: Choose New Project from the File menu and then select Cocoa Bundle from the list of project types. For the name of the bundle enter the same name you intend to give to the name of your NSColorPicker subclass.

The next step is not absolutely necessary, but is helpful for debugging and testing the custom color picker. As shown in Figure 1, double-click the project folder in Xcode and, in the General pane of the project preferences, set the build product output directory to Library/ColorPickers in your home directory.

Figure 1  Specifying the location for the built color picker
Specifying the location for the built color picker

Next set the extension of the color-picker bundle: Double-click the bundle target, select the Build pane, and search for the Wrapper Extension setting. Change the value of this setting to “colorPicker”, as shown in Figure 2.

Figure 2  Setting the bundle extension
Setting the bundle extension

The final step in project configuration is to set two properties: the bundle identifier and the principal class. The name of the principal class should be the same as the name of the project. Figure 3 illustrates what these settings might look like.

Figure 3  Setting bundle identifier and principal class
Setting bundle identifier and principal class

Now let’s leave Xcode for awhile and switch to Interface Builder. The Cocoa Bundle project type does not include a nib file, so you’ll have to add one. Launch the application and in the Starting Point panel select Empty under Cocoa and click New (see Figure 4).

Figure 4  Creating a nib file for the color picker
Creating a nib file for the color picker

Drag a Custom View object from the Containers palette onto your nib file window (this step requires macOS 10.4 or greater). Then construct the user interface of your color picker on this view with palette objects (if necessary). Figure 5 gives an example of what you might see in Interface Builder. Size the view so that it is commensurate with the NSSize value returned from the NSColorPicker implementation of the NSColorPickingDefault protocol method minContentSize. Save the nib file under an appropriate name and then, in Xcode, add the nib file to the project as a localized resource.

Figure 5  The nib file window for the color picker
The nib file window for the color picke

Now comes time for some coding, starting with your custom subclass of NSColorPicker. In Xcode, create the files for a new Objective-C class and add them to the project (File > New File). Give the header and implementation files the same name (minus the extensions) as that of the project. Open the header file and change the superclass to NSColorPicker and declare that the class adopts the NSColorPickingCustom protocol. Also add any outlets you require to the view containing the color picker’s user interface. Listing 1 illustrates what the header file declarations should look like.

Listing 1  Declarations of the NSColorPicker subclass

@interface RadiantColorPicker: NSColorPicker <NSColorPickingCustom> {
    IBOutlet NSView *_pickerView;
    IBOutlet RadiantColorControl *_radiantColorControl;
}
@end

To keep Interface Builder synchronized with these changes, drag the header file from Xcode and drop in on the nib file window. Select the File’s Owner icon in the nib file window, open the Custom Class inspector pane (Command-5), and select the NSColorPicker subclass you just declared. Then connect any outlets you defined for that class.

At this point, create and add files for any custom classes needed for the user interface of the color picker. As with the custom NSColorPicker subclass, added any required declarations to the header file and then drag this file onto the nib file window to make Interface Builder aware of the custom view. Assign the custom view class using the Custom Class inspector pane. You may need to do this before making any outlet connections between the custom view class and your NSColorPicker subclass.

Implement your NSColorPicker subclass by, at the least, implementing the methods of the NSColorPickingCustom protocol and, optionally, re-implementing methods of the NSColorPickingDefault protocol, as necessary. Listing 2 provides an example.

Listing 2  Sample implementation of the custom NSColorPicker class

@implementation RadiantColorPicker
 
- (id)initWithPickerMask:(NSUInteger)mask colorPanel:(NSColorPanel *)owningColorPanel {
    return [super initWithPickerMask:mask colorPanel:owningColorPanel];
}
 
- (void)dealloc {
    [_pickerView release];
    [super dealloc];
}
 
- (BOOL)supportsMode:(NSColorPanelMode)mode {
    return (mode == NSRGBModeColorPanel) ? YES : NO;
}
 
- (NSColorPanelMode)currentMode {
    return NSRGBModeColorPanel;
}
 
- (NSView *)provideNewView:(BOOL)initialRequest {
    if (initialRequest) {
        // Load our nib files
        if (![NSBundle loadNibNamed:@"RadiantColorPicker" owner:self]) {
            NSLog(@"ERROR: couldn't load MyColorPicker nib");
        }
        [_radiantColorControl setTarget:self];
        [_radiantColorControl setAction:@selector(colorChanged:)];
    }
    return _pickerView;
}
 
- (void)colorChanged:(id)sender {
    [[self colorPanel] setColor:[_radiantColorControl color]];
}
 
- (void)setColor:(NSColor *)newColor {
    [_radiantColorControl setColor:newColor];
}
 
- (NSString *)buttonToolTip {
    return NSLocalizedString(@"Radiant Picker", @"Tooltip for the radiant color picker button in the color panel");
}
 
@end

There are a few things to note about this code:

Implement the custom view objects for your color picker as necessary.

When you have finished implementing and testing your custom color picker, and it is a third-party product, create an installer for it that puts in one of the prescribed locations.