Color Picker for iOS

Apple has always been known as a leader in popularizing computer graphics technology, and iOS is no exception, with its animations, sensor-enhanced visuals, and snappy UI responsiveness. So it is surprising that the iOS SDK is rather lacking when it comes to some basic color handling functions, as others have noted. Perhaps it is even more surprising that despite the proliferation of iPad drawing applications there is no built-in color choosing widget, leaving all those app developers to create their own, with varying results of quality and usefulness. To right this wrong, the folks over at Vevent put together an open source color picker called Hue back in 2009. Fabián Cañas noted a couple of issues with their implementation; this is my attempt to fix a couple more.

ColorPickerController

Without further ado, allow me to present ColorPickerController and the simple ColorPicker test application that shows how it works. The screenshot at right should give you an idea of the interface, and you can download the source code from github here: https://github.com/mateagar/Color-Picker-for-iOS Using ColorPickerController is easy enough. I designed it to work inside a UINavigationController stack; you can easily turn it into a modal view controller or (on iPad) display it inside a UIPopoverController, too. ColorPickerController defines a delegate protocol (ColorPickerDelegate), with simple save and cancel methods that your calling code can implement:  
- (void)colorPickerSaved:(ColorPickerController *)controller;
- (void)colorPickerCancelled:(ColorPickerController *)controller;
  You initialize ColorPickerController by passing in a UIColor object and a title. When the user is finished and taps the save button you can read the new color using the selectedColor property:  
- (void)showColorPicker {
    ColorPickerController *colorPicker =
        [[ColorPickerController alloc] initWithColor:[UIColor redColor]
                                            andTitle:@"Color Picker"];
    colorPicker.delegate = self;
    [_navigationController pushViewController:colorPicker
                                     animated:YES];
    [colorPicker release];
}

- (void)colorPickerSaved:(ColorPickerController *)controller {
    UIColor *newColor = controller.selectedColor;
    // do something with newColor
    [_navigationController popViewControllerAnimated:YES];
}
 

Color Conversion Methods

In addition to the basic interface widget described above, ColorPickerController also exports a handful of class-level methods that it uses internally which you may find useful in your own code.  
+ (NSString *)hexValueFromColor:(UIColor *)color;
Given a UIColor object, this method returns a 6-character hexadecimal representation of the color as used widely on the web in CSS and so forth.  
+ (UIColor *)colorFromHexValue:(NSString *)hexValue;
This is the inverse to the previous method, creating a UIColor object from the hexadecimal string provided.  
+ (BOOL)isValidHexValue:(NSString *)hexValue;
If you are getting your hexadecimal strings from user input, you may want to first check to see whether they are valid using the above method.  
+ (HsvColor)hsvColorFromColor:(UIColor *)color;
Although iOS provides an initializer for creating a UIColor object from hue, saturation, and brightness values, it does not provide a way to read back those values from a UIColor object. This method returns the color information in HSV space using a struct. The HsvColor struct actually contains six values. hue, saturation, and brightness are all CGFloat numbers between 0 and 1, which you can store and use later to instantiate UIColor objects as necessary. There are also three companion values -- hueValue, saturationValue, and brightnessvalue -- which provide integer representations that are more appropriate for user display. hueValue is the color hue as measured in degrees (i.e., between 0 and 359). saturationValue and brightnessValue are both percentages between 0 and 100.  
+ (RgbColor)rgbColorFromColor:(UIColor *)color;
Although extracting red, green, and blue (RGB) values from UIColor is relatively straightforward (by way of CGColor and CGColorGetComponents), this method returns both the CGFloat values (red, green, blue, each between 0 and 1) and the integer representations (redValue, greenValue, blueValue, each between 0 and 255) which are a bit more user-friendly.

Implementation Notes

As I mentioned earlier, Vevent did a nice job with their initial implementation. I used their project as an reference, but this version represents a ground-up rewrite so that I could address a number of minor details.
  1. Accurate display of brightness gradient. As documented by Fabián Cañas, the original Vevent implementation simply gets this one wrong -- 100% brightness does not equal the color white (100% brightness + 0% saturation does, however).
  2. Accurate display of hue-saturation gradient. One thing that bothered me about the original implementation (and color picker implementations on many other platforms as well, I might add) is that the color selected in the hue-saturation gradient only looks like the actual color chosen when the brightness level is at 100%. This implementation synchronizes the hue-saturation gradient with the brightness gradient so that you can see the actual colors available at your current brightness level without having to guess.
  3. Better handling of colors at the edges of the gradients. In the original implementation, it was almost impossible to select a color right at an edge of either the brightness or the hue-saturation gradients, because the code only handled touches inside those views. By tracking the location of the initial touch and then allowing the user to drag outside the view, it is much easier to select the entire range of colors by touch.
  4. Allowance for color value inputs. I was partial to the layout that Vevent originally presented, but having the color values present onscreen as read-only labels just seemed like a tease. They are now editable, with full input validation (just try to crash the app using incorrect color values… I double dog dare you).
  5. Delegate implementation with Save and Cancel buttons. As Fabián pointed out, this just makes the whole thing a lot more useful.
  6. Support for varying screen sizes and rotations. In 2009 there was no iPad, and all iOS devices had the same resolution. Now we have iPads and the Retina Display, and it seemed a shame to have a fixed size for this control. This version supports full screen on iPad in both landscape and portrait orientations, as well as full resolution Retina Display output.
  7. Improved use of Objective-C syntax. I'll admit it -- I'm kind of a code snob. I don't like hard-coded values, brute-force methods, or obtuse variable names. Little things like language localization, making instance variables private, and using dynamic properties rather than throwing up additional method calls to work around them are probably not important to most people, but I think the overall result is easier to follow and to use.