How to Extend the OS X Color Panel with a Custom Color Picker?

There is a tool that I have been using for years now without giving it much thought. It does a simple tasks of picking and applying of color. This illusory simplicity seems to have blinded me to the whole concept hidden behind it. I’m sorry Color Panel, let me redeem myself by telling people how to extend you.

Every Mac user is more or less familiar with the Color Panel, the small “Colors” window pops up in many applications when the user has to select a color. From text formatting in Mail, through shape editing in Preview, to storyboards or XIB customization in Xcode − most system apps make use of OS X Color Panel, and it’s even supported by a range of third-party apps, including Pixelmator or Photoshop. swift

It provides five default color pickers, described exhaustively in an article by Robin Wood. The Pixelmator team also has a nice summary of the matter. I’m a software developer, so I’ll show you how to create your own color picker in Swift and add it to the system Color Panel. I myself learned how to do this from an extremely outdated Apple guide published in 2013 and by reverse-engineering a couple of third-party pickers.

OSX Color Panel

Still not sure what I’m talking about? Scroll to the very bottom and watch the video presenting the result of the efforts we’ll be outlining in this post. Also, grab the full project source code here.

Before We Begin

Color pickers are distributed as bundles. Although furnished with their own .colorPicker extension, they are just directories getting “special treatment” from the OS. Once they’re put in ~/Library/ColorPickers/, they appear in the system’s Color Panel.

So, we’re going to set up an Xcode project to produce the bundle, then copy it to ~/Library/ColorPickers/ and launch the “Colors” window. To achieve the latter, we may open, let’s say, TextEdit and attempt to change the font color… but we’re smart and we’ll prepare a separate Color Panel launcher using AppleScript.

To do so, bring up Spotlight Search using the ⌘ + Space shortcut and type in “Script Editor.app”. Choose “New Document” and type in this AppleScript command:

choose color

and save it as an “application” somewhere on your computer (from now on I will be referring to it as the “Open Color Panel” app). If you double tap its icon, the “Colors” window will appear. Cool! Now, let’s add a custom color picker in there.

Project Setup

Launch Xcode and create a new project. The Color picker is a bundle, so choose the “OS X > Framework & Library > Bundle” template.

Name it “DemoColorPicker” and put in your organization name and identifier. For the bundle extension, type in “colorPicker”.

Add a new Swift file: DemoColorPicker.swift (we’re not going to use Objective-C, so don’t create a bridging header). In this file, we’ll define the DemoColorPicker class−a main class for our picker. Finally, we need to tell the Color Panel that DemoColorPicker is the entry point of our bundle. In the OS X bundle architecture, we use the “Principal class” property for this purpose. Go to the “DemoColorPicker” target’s “Info.plist” and set “Principal class” to DemoColorPicker.DemoColorPicker (following the product_name.class_name structure):

Color Panel will load your bundle and search its binary for principal class. Then it will instantiate it and use it if it implements NSColorPickingCustom protocol from AppKit. Here’s the code:

// DemoColorPicker.swift

import Foundation
import AppKit


public class DemoColorPicker: NSColorPicker, NSColorPickingCustom {

    public func supportsMode(mode: NSColorPanelMode) -> Bool {
        return true
    }

    public func currentMode() -> NSColorPanelMode {
        return .NSNoModeColorPanel
    }

    public func provideNewView(initialRequest: Bool) -> NSView {
        // Temporary, to only see if it works

        let emptyView = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 300))
        emptyView.wantsLayer = true
        emptyView.layer?.backgroundColor = NSColor.lightGrayColor().CGColor
        return emptyView
    }

    public func setColor(newColor: NSColor) {

    }

    public override var minContentSize: NSSize {
        return NSSize(width: 300, height: 400)
    }

}

Every app launching Color Panel may enabled / disable color pickers by providing different modes. Default pickers have dedicated modes listed in NSColorPanelMode enum. I want my picker to be always enabled, so I return true in supportsMode. With this, .NSNoModeColorPanel sounds like a good value for currentMode. In provideNewView, we should return the view for our picker’s UI. For now, I just create empty view with light gray background to see if it works. Later, I’ll replace it with view loaded from XIB. The setColor method will be described soon. The last part – minContentSize, is computed property that defines the minimal size of color picker view. User won’t be able to resize the window below this boundary. This method is not required by NSColorPickingCustom.

That’s all what we need for now. Let’s try it! Build the project (⌘ + B), find DemoColorPicker.colorPicker product under “Products” group in Xcode’s project navigator. Copy it and paste to ~/Library/ColorPickers/. Now, run the “Open Color Panel” app that we prepared earlier. The “Colors” window should appear and… ops, our picker seems to not be loaded. Time for debugging…

Debugging with Console.app

We can’t connect Xcode’s debugger to our color picker running inside another app to see what’s wrong. Instead, I came up with different solution – OS X Console. This utility logs all runtime exceptions and messages printed with NSLog() function (note: it doesn’t support Swift’s print, but you can still use NSLog() in Swift code).

Call out the Spotlight Search (⌘ + Space) and type in “Console.app”. With “All Messages” selected, enter “applet” (or “Demo”) in search field to filter out all unnecessary logs. Run the “Open Color Panel” again. It will print an exception:

The “Library not loaded: @rpath/libswiftAppKit.dylib” exception was raised by our bundle because we didn’t tell the compiler that we ship Swift code in it. In result, the host app didn’t know to load AppKit dynamic library at runtime and it failed when trying to use it.

Fortunately, it’s easy to fix. Go back to Xcode, click on the project icon in navigator and select “DemoColorPicker” target. On “Build Settings” tab, set “Embedded Content Contains Swift Code” setting to “YES”. Because we didn’t change anything in code, to have this change applied, perform deep clean (select “Product > Clean Build Folder…” while holding Option key) and build the bundle again (⌘ + B).

Once again, open DemoColorPicker.colorPicker in Finder and copy and paste it to ~/Library/ColorPickers. Run the “Open Color Panel”. The “Colors” window should appear and… because we didn’t provide the toolbar icon, you won’t see the picker first off. Just click next to “Pencils” icon and our picker will appear. Cool!

Adding custom icon

Let’s solve this icon issue at first. Prepare 48x48px, 144px/inch resolution, uncompressed ToolbarIcon.tiff image and add it to the project. Now, override provideNewButtonImage computed property and return the NSImage for our icon:

private let bundle = NSBundle(forClass: DemoColorPicker.self)

public override var provideNewButtonImage: NSImage {
    let icon = bundle.imageForResource("ToolbarIcon")!
    return icon
}

Notice, that I’m specifying the bundle for picker class. I can’t use NSBundle.mainBundle() since it will return bundle of the app that launched the Color Panel (for instance /Applications/TextEdit.app if we launch the picker from TextEdit app).

Build, install, and enjoy the custom icon!

Advanced UI

I will be using XIB for the custom UI. To do so, create DemoColorPickerViewController.xib by opening “File > New > File…” and selecting the “View” template in the “OS X User Interface” group.

Set DemoColorPicker as the “File’s Owner” class name:

and connect “Custom View” to the pickerView outlet in DemoColorPicker.swift:

Below you can see how I changed the provideNewView method implementation to load the view from XIB. For performance reasons, it’s important to do it for initialRequest. Once loaded, the view will be attached to the pickerView property, so I can safely return it from the provideNewView method.

@IBOutlet var pickerView: NSView!

public func provideNewView(initialRequest: Bool) -> NSView {
    if (initialRequest) {
        let pickerNibName = "DemoColorPickerViewController"
        guard bundle.loadNibNamed(pickerNibName, owner: self, topLevelObjects: nil) else { fatalError() }
    }

    return pickerView
}

Now, we are ready to add some serious color picking functionality.

Handling Color Changes

The primary objective of every color picker is to communicate with the host app to send and receive colors. Let’s take a look at two scenarios. A user highlights a text passage in TextEdit and would like to use our picker to change its color. Also, using the eyedropper tool, the user would like to pick a color from a different window and have it received by our picker – for example, render it on the palette. I’m going to implement both of those scenarios.

First, we need some sort of UI. Since we’re using XIB, that part is trivial. Here, I added two buttons, one label, and three views:

I want the color to be applied in the host app when the user taps either the “apply red” or the “apply blue” button. Moreover, when the user selects a color in a different color picker or uses the eyedropper tool, I want to render the selected color in the upper view:

@IBOutlet weak var selectedColorRenderingView: NSView!
@IBOutlet weak var selectedColorValueLabel: NSTextField!


public func setColor(newColor: NSColor) {
    renderColor(newColor)
}

@IBAction func selectRed(sender: AnyObject) {
    colorPanel.color = NSColor.redColor()
}

@IBAction func selectBlue(sender: AnyObject) {
    colorPanel.color = NSColor.blueColor()
}

private func renderColor(color: NSColor) {
    selectedColorRenderingView.wantsLayer = true
    selectedColorRenderingView.layer!.backgroundColor = color.CGColor
    selectedColorValueLabel.stringValue = "color: \(color)"
}

Sending color to the host app is simple. I added two IBActions for buttons and assigned the right color to the colorPanel.color property. colorPanel is a NSColorPanel instance that did load our color picker bundle. It’s the guy we should speak with in terms of applying / receiving color.

Receiving color is even more straightforward. Simply implement the setColor method from the NSColorPickingDefault protocol. I used it to change the background color of the upper view and to update the label.


That’s it! You can now use this picker to apply either red or blue in any app using the Color Panel. It can also pick the color from any window and render it on palette. At this point, it’s up to you what sort of features you’d like to add later. You can, for example, display the color list using NSColorList and NSTableView or go the whole hog and build an interactive, shiny 3D RGB cube.

In Summary

You should now be ready to build your own color picker. What’s important, it is not that different from usual Mac OS X app development. However, if you need additional technical knowledge on colors and color spaces, take a look at Apple’s Introduction to Color Programming Topics for Cocoa. Write a comment or tweet me at @ncreated if you have any further questions.

And finally, below you can find the video demonstrating the color picker we created in this post. The source code can be found here.

If you are interested in extending other OS X services, like the spell-checker, read Michał’s thoughts on How (Not) to Create a Spell Server for Mac OS X. To be up to date with other Open Source projects we do here at Macoscope, have a look at Grand Central Board post from Oktawian.

8 Tech-Related Blog Posts You May Have Missed in Q1 on the Macoscope Blog
Storyboards and Their (Better) Alternatives