iPhone development: accessing UIColor components

I recently got into a discussion with some developers about the general impenetrability of the UIColor class on the iPhone. For those who aren't familiar, UIColor is a standard SDK class that provides color representation on the iPhone. UIColors add color information to many UIKit objects. For example, you might use the class to set the font color for a UILabel for setting the background color of a UIView. UIColor offers the basic color object for most SDK color tasks. At the same time, UIColor acts as a blunt object for solving color tasks. There's really not much you can do with the class other than creating new instances and applying them to UIKit objects.

If you have been working with Quartz for any time, you're likely to have spent more time working with Core Graphics' CGColor than with UIColor. The advantage of UIColor lies in its SDK compliance and its iPhone-specific set of color spaces. But beyond that, Apple basically assumes that the developer will have no further interest in exploring colors. Apple writes, "The only time [subclassing UIColor] might be necessary is if you require support for additional colorspaces or color models."

What Apple doesn't mention is that developers may want to pull more information out of UIColor and work with the class in ways that are not yet available. So, while one might not want to subclass, from a developer point of view, it is worth exploring ways to expand UIColor. Expanding a class lets you add new methods and properties and bring out features that Apple might have overlooked. That's because UIColor doesn't offer as wide a feature set as many developers could use and take advantage of.

In this post, I'm going to introduce a number of ways I have expanded UIColor and show where the class could be built upon even further. Much of this work is built on the feedback and contributions of Hans Larsen, Kevin Ballard, and Emanuele Vulcano, all of whom helped nursemaid this little project along in #iphonedev over at irc.freenode.net.

The problems with UIColor

Every UIColor offers a hidden Core Graphics CGColor inside. You can pull out the underlying CGColor from any UIColor instance via its CGColor property. Doing so gives you access to all the C-based Quartz utilities that allow you to view and manipulate CGColor references. CGColorRefs contain the components that define a color and the color space that interpret those components. Functions like CGColorGetComponents() and CGColorGetNumberOfComponents() pull values from the CGColor and return them to the developer as requested.

Unfortunately, Apple has not built many similar utilities into its SDK-based UIColor class. For example, you cannot access the internal color space and the components that make up that color without using Core Graphics functions on the internal CGColor. UIColor doesn't define "red" or "alpha" methods or properties to let you examine a color in the same way that you construct it in the first place. And these would be quite handy in practical development.

Contrast this lack of properties with the color constructors provided by UIColor. The constructive methods are explicit. Consider colorWithRed:green:blue:alpha: for example. It lets you specify the four components that make up your color. UIColor supports creating colors with RGB, Monochrome, and hue/saturation/brightness (HSB) values. There is also a special kind of "pattern" color, which is used to create patterned backgrounds. Each of these constructors makes it easy to produce colors on demand. But after you have built the instance, there's no way to go back and recover those elements that you put into the color when you built it.

Another problem with UIColor lies in the strings department. UIKit does not provide the kind of convenience string functions for UIColors that it does for CGRect and CGPoint structures. With NSStringFromCGRect() for example, you can convert a CGRect struct into a human-readable string that you can then store in your user defaults. You can retrieve the CGRect back from the string too. CGRectFromString() converts the string you built into a proper CGRect. Surely, UIColor could benefit from that kind of processing. Doing so gives you an elegant way to save to file using string representations and to read colors back in from strings.

Expanding UIColor

This "Hello World" test bed is part of the github archive

One of the amazing features of Objective-C is its built-in ability to expand classes that are already available from the SDK's API. Rather than subclassing, you can build new behavior that extends the functionality for the class that's there. To add this to your projects, just build a set of header and implementation files with a descriptive expansion name. For example in my UIColor-Expanded.h header file, I declared a new @interface section. Here is where I define new method prototypes and new proprties. The same holds for the @implementation in the .m file.

@interface UIColor (expanded)
Add new methods and so forth here
@end

Colorspaces

The Core Graphics color spaces are an enumerated set of models supported by the iPhone platform. The iPhone supports seven color models plus an "unknown" model. As a rule, the iPhone uses the RGB color space for most normal colors except for grays. For grays, including all shades from black to white, it uses the Monochrome color space.

One of the first things I wanted to expose was the color space used for any UIColor and I did this in two ways: providing both the enumerated result and an NSString that described the color space in use. I built colorSpaceModel and colorSpaceString to provide these. As I already suggested, the color space model used for [UIColor whiteColor] is different from [UIColor blueColor]. The former is monochrome, the latter RGB.

Accessing components

After sorting out the color spaces, I decided to expose each of the components for any non-pattern UIColor. To do this, I created four new properties: red, green, blue, and alpha. Like any other properties, these can be used with standard Objective-C getters, i.e. myColor.blue, and each one returns a CGFloat. This seemed like a natural outgrowth of the UIColor object, simply opening the color components up to the outside world.

To get at those components, I took advantage of the Core Graphics utility CGColorGetComponents(). This returns either two components (for Monochrome colors) or four (for RGB) as a C array of CGFloats. I check for the two supported color space models and then return a floating point value depending on which model is in use. A simple utility method, canProvideRGBComponents, lets you check if RBBA values can be extracted from any UIColor object.

Working with strings

I put out a call on Twitter for UIColor utility suggestions and soon learned that developers wanted support not just for string conversion but also for hex colors, typically used on the Web. So when designing the string utilities, I made sure to support not just a pretty string conversion (comma delineated numbers in braces) but reading from and writing to hex strings.

I ended up building four methods. Two are instance methods, which return an NSString representation of the color in use. Two are class methods, which build new colors from string descriptions. They are:

These methods do not follow the CGRect/CGPoint examples in being implemented as functions outside of C classes. Instead, I decided to implement them as methods; they tie well into class and instance functionality. The code is quite simple. I support reading hex values that start with 0x, such as 0xFFFFFF as well as those that do not. For descriptions, I save to three decimal places, which seems sufficient for most developers' color needs.

Source code

You can download a copy of the source code from github. It's hosted in the UIColor Utilities repo. This is still a project under development and I have a growing request list for enhancements.

If this little project demonstrates anything, I hope you walk away with the understanding that the SDK API is a jumping off point. It can be grown and expanded upon and not just by Apple. When Apple's classes and methods do not perform the way you need them to, you can grow those items to give them the extra functionality you need.