UIColor

UIColor is a commonly used class that represents color and sometimes opacity. As it turns out, UIColor is a class cluster made up of a couple of private concrete subclasses. Class clusters group a number of private concrete subclasses under a public abstract superclass; this is based on the abstract factory design pattern.¹

Red, Green, Blue

As of iOS 6, subclasses of the UIColor class cluster include UIDeviceWhiteColor and UIDeviceRGBColor. Unfortunately for the programmer, only UIDeviceRGBColor knows what red, green, and blue are, making it challenging to get the color components from a generic UIColor object.

According to Apple, UIColor’s method getRed:green:blue:alpha: will return the red, green, and blue color components if the color is within the RGB color space. If the color is in a compatible color space, the color is converted into RGB format and its components are returned to your application. If the color is not in a compatible color space, the parameters are unchanged.² And therein lies the problem.

Say we have an API that accepts a UIColor object and uses its color components for some arbitrary computation. One might think, “getRed:green:blue:alpha to the rescue!” Sadly, this bruteforce approach will only work if the UIColor object is of the RGB color space, or the UIDeviceRGBColor class. The only approach is to inspect and retreive the color components using the Core Graphics functions CGColorGetNumberOfComponents(color.CGColor) and CGColorGetComponents(color.CGColor). This approach quickly doesn’t scale if you are constantly retreiving the color components for various reasons, so it would be ideal if UIColor handled this for us under the hood. For the curious, I have created a radar in hopes to address this issue.

A Universal Selector

Luckily for us, we have objc/funtime.h that will provide exactly what we want. Using method swizzling, we can create an alternative to getRed:green:blue:alpha that checks the number of the color components using getRed:green:blue:alpha for UIDeviceRGBColor objects and getWhite:alpha for UIDeviceWhiteColor objects. The color components from UIDeviceWhiteColor objects are calculated by using the white balance as a multiplier for each RGB component. Using this approach, we have successfully moved the redundant color space checking behind the scenes and provided ourselves with much cleaner code snippets. The category implementation is below.

#import <objc/runtime.h>

@implementation UIColor (ColorComponents)

+ (void)initialize
{
    if (self == [UIColor class])
    {
        Method oldMethod = class_getInstanceMethod(UIColor.class, @selector(getRed:green:blue:alpha:));
        Method newMethod = class_getInstanceMethod(UIColor.class, @selector(eds_getRed:green:blue:alpha:));
        method_exchangeImplementations(oldMethod, newMethod);
    }
}

- (BOOL)eds_getRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha
{
    if (CGColorGetNumberOfComponents(self.CGColor) == 4) {
        return [self eds_getRed:red green:green blue:blue alpha:alpha];
    }
    else if (CGColorGetNumberOfComponents(self.CGColor) == 2) {
        CGFloat white;
        CGFloat m_alpha;
        if ([self getWhite:&white alpha:&m_alpha]) {
            *red = white * 1.0;
            *green = white * 1.0;
            *blue = white * 1.0;
            *alpha = m_alpha;
            return YES;
        }
    }
    return NO;
}

@end

References