Thursday, March 19, 2009

Gradient Shading UIView

Today I want to do something similar to the UIRoundedView, except this time I want the background to be coloured using a gradient shading. Something like this:

So, like last time, we will extend the UIView class. We are going to require an extra class property, which is going to be the one of 2 colours we use to specify our gradient. We will use the backgroundColor property as the other one. We are only concerned with a linear vertical gradient... of course it would be easy to extend this class to handle other types of gradient, but this is all I need for now. We are going to use CGContextDrawLinearGradient to create the effect, which takes a CGGradientRef, so lets include that in our class definition as well:

#import <UIKit/UIKit.h>

@interface UIGradientView : UIView {
CGGradientRef gradient;
UIColor *gradientColor;

@property (nonatomic, retain) UIColor* gradientColor;

Next up, we want to create the CGGradientRef based on gradientColor and backgroundColor. One thing that was a little tricky here is that the colour spaces of each colour passed to CGGradientCreateWithColorComponents must be the same. However, [UIColor *Color] doesn't always return the same colour space. So we need some kind of conversion, which we need to perform for both backgroundColor and gradientColor.

CGGradientCreateWithColorComponenets takes a colour space, an array of colour components, an array of locations, and a count.

First up, we need to figure out how many components the colour has:

CGColorRef col = self.backgroundColor.CGColor;
CGColorSpaceRef colSpace = CGColorGetColorSpace(col);
NSLog(@"Color Space contains: %d", CGColorSpaceGetNumberOfComponents(colSpace));

We extract the components using CGColorGetComponents which will return a float array with n + 1 entries, where n is the value returned above.

const CGFloat *bgCols = CGColorGetComponents(col);
if(CGColorSpaceGetNumberOfComponents(colSpace) == 1) // White Color Space
colors[0] = bgCols[0];
colors[1] = bgCols[0];
colors[2] = bgCols[0];
colors[3] = bgCols[1];
colors[0] = bgCols[0];
colors[1] = bgCols[1];
colors[2] = bgCols[2];
colors[3] = bgCols[3];

The extra value returned is the alpha value. Conversion from a gray scale colour scheme to rgb, is simple, we just assign the same value to R G and B entries. We need to repeat this process for the gradient colour, and then we create our CGGradientRef:

gradient = CGGradientCreateWithColorComponents(rgb, colors, NULL, sizeof(colors)/(sizeof(colors[0])*4));

Now that we have created the gradient, we can do the actual drawing. Same as last time, we will be overriding drawRect:, but the code is pretty trivial this time:

- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();

CGPoint start = CGPointMake(rect.origin.x, rect.origin.y);
CGPoint end = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height);

CGContextClipToRect(context, rect);

CGContextDrawLinearGradient(context, gradient, start, end, 0);

And thats about it as far as interesting code is concerned. We should create the gradient as few times as possible, so when either the backgroundColor or gradientColor change, and we need to define some behaviour if only one is set (ie if backgroundColor is set, but gradientColor is not, just fill the background as per usual).


[UIColor whiteColor] [UIColor blackColor] [UIColor grayColor] all return a colour in a colour space with a single component, where as [UIColor redColor] returns an RGB colour space.