Last Updated: February 25, 2016
·
2.094K
· samd

iOS Colour Mixing Class

In order to vary colour between 2 or more colours according to a value we great a the following ColourMixer class.

We provide it with an array of boundaries and colours and then query it with a value to find our required colour.

This class supports 2 different mixing methods - discrete, where the outputs are not mixed, or gradient where mixing occurs.

ColourMixer.h

typedef enum {
    ColourMixerGradient,
    ColourMixerDiscrete
} ColourMixerType;

@interface ColourMixer : NSObject

@property (nonatomic, retain) NSArray *colours;
@property (nonatomic, retain) NSArray *boundaries;
@property (nonatomic, assign) ColourMixerType type;

- (UIColor *)colourForValue:(id)value;

@end

ColourMixer.m

#import "ColourMixer.h"

@implementation ColourMixer

@synthesize colours = _colours;
@synthesize boundaries = _boundaries;
@synthesize type = _type;


- (id)init {
    self = [super init];
    if(self) {
        [self setDefaults];
    }
    return self;
}

-(void)dealloc
{
    [_colours release], _colours = nil;
    [_boundaries release], _boundaries = nil;
    [super dealloc];
}

-(void)setDefaults
{
    self.type = ColourMixerDiscrete;
    self.colours = [[[NSArray alloc] initWithObjects:[UIColor redColor], [UIColor orangeColor], [UIColor greenColor], nil] autorelease];
    self.boundaries = [[[NSArray alloc] initWithObjects:[NSNumber numberWithFloat:0.5], [NSNumber numberWithFloat:0.8], nil] autorelease];
}

- (UIColor *)colourForValue:(id)value
{
    if(self.type == ColourMixerDiscrete) {
        if (self.boundaries.count != (self.colours.count - 1)) {
            return nil;
        }
        for (int i=0; i < self.boundaries.count; i++) {
            if([value compare:[self.boundaries objectAtIndex:i]] == NSOrderedAscending) {
                return [self.colours objectAtIndex:i];
            }
        }
        return [self.colours lastObject];
    } else {
        // Must be a continuous gradient
        if (self.boundaries.count != self.colours.count) {
            return nil;
        }
        // If lower than first boundary then return first colour
        if ([value compare:[self.boundaries objectAtIndex:0]] == NSOrderedAscending) {
            return [self.colours objectAtIndex:0];
        }
        // If bigger than last boundary then return last colour
        if ([value compare:[self.boundaries lastObject]] == NSOrderedDescending) {
            return [self.colours lastObject];
        }
        // Otherwise, need to return a mixed colour
        for (int i=1; i < self.boundaries.count; i++) {
            if([value compare:[self.boundaries objectAtIndex:i]] == NSOrderedAscending) {
                // So the mix should be between the (i-1)th and ith colours
                double range = [[self.boundaries objectAtIndex:i] doubleValue] - [[self.boundaries objectAtIndex:(i-1)] doubleValue];
                double ratio = ([value doubleValue] - [[self.boundaries objectAtIndex:(i-1)] doubleValue]) / range;
                return [self mixColour:[self.colours objectAtIndex:(i-1)] andColour:[self.colours objectAtIndex:i] withRatio:ratio];
            }
        }
        // Should never be able to get here...
        return nil;
    }
}

- (UIColor *)mixColour:(UIColor*)firstColour andColour:(UIColor*)secondColour withRatio:(double)ratio
{
    if(ratio <= 0)
        return firstColour;
    if(ratio >= 1)
        return secondColour;

    /* There is a nicer way to do this in iOS > 5, but for backwards compatibility we do it this way */
    CGColorRef colorref = [firstColour CGColor];
    const CGFloat *components1 = CGColorGetComponents(colorref);
    CGFloat r1 = components1[0];
    CGFloat g1 = components1[1];
    CGFloat b1 = components1[2];
    CGFloat a1 = components1[3];

    colorref = [secondColour CGColor];
    const CGFloat *components2 = CGColorGetComponents(colorref);
    CGFloat r2 = components2[0];
    CGFloat g2 = components2[1];
    CGFloat b2 = components2[2];
    CGFloat a2 = components2[3];

    CGFloat r3 = (r2 - r1) * ratio + r1;
    CGFloat g3 = (g2 - g1) * ratio + g1;
    CGFloat b3 = (b2 - b1) * ratio + b1;
    CGFloat a3 = (a2 - a1) * ratio + a1;

    return [UIColor colorWithRed:r3 green:g3 blue:b3 alpha:a3];
}

@end