Custom gradient controls for iOS

23rd
Jan
2012

Recently I needed custom UI control for iOS that had rounded corners and a gradient background. To make the user experience as rich as possible I wanted the background of the control to flatten when the user tapped it. There were plenty of examples of gradient buttons and rounded corner views but nothing did exactly what I needed.

After a bit of experimentation I found the easiest solution was to add a gradient layer to a custom UIControl and swap it with a different layer when the control became highlighted.

The first step was to was to create a custom UIControl class and add a gradient background layer:

GradientControl.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#import <QuartzCore/QuartzCore.h>

@interface GradientControl : UIControl
{
CAGradientLayer *normalBackground;
}
@end

@implementation GradientControl
- (id) initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
self.backgroundColor = [UIColor clearColor];

normalBackground = [CAGradientLayer layer];
normalBackground.frame = self.bounds;
normalBackground.cornerRadius = 10;
normalBackground.borderWidth = 1;
normalBackground.borderColor = [[UIColor whiteColor] CGColor];

normalBackground.colors = [NSArray arrayWithObjects:
(id)[[UIColor colorWithHexString:@"a9aeba"] CGColor],
(id)[[UIColor colorWithHexString:@"7e8790"] CGColor],
(id)[[UIColor colorWithHexString:@"6f778b"] CGColor],
(id)[[UIColor colorWithHexString:@"5b657d"] CGColor],
nil];
normalBackground.locations = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:0.51],
[NSNumber numberWithFloat:1],
nil];
}

return self;
}
@end

Next we need to add a UIView to our main view in Interface Builder and set the class property to our new custom GradientControl:

Adding control in Interface Builder

Now if we run it in the simulator we can see our new shiny rounded gradient control:

Rendered control

Although this new component responds to events and triggers any actions that you assign it doesn't give any visual feedback to taps. This can be solved by creating a second gradient layer and swapping the layers when the highlight value changes:

GradientControl.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#import <QuartzCore/QuartzCore.h>

@interface GradientControl : UIControl
{
CAGradientLayer *normalBackground;
CAGradientLayer *highlightBackground;
}
@end

@implementation GradientControl
- (id) initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
self.backgroundColor = [UIColor clearColor];

normalBackground = [CAGradientLayer layer];
// setup normal background...

highlightBackground = [CAGradientLayer layer];
// setup highlight background...
}

return self;
}

- (void) setHighlighted:(BOOL)aHighlighted
{
BOOL oldHighlighted = self.highlighted;
[super setHighlighted:aHighlighted];

if (oldHighlighted == aHighlighted)
{
return;
}

if (aHighlighted)
{
[self.layer replaceSublayer:normalBackground with:highlightBackground];
}
else
{
[self.layer replaceSublayer:highlightBackground with:normalBackground];
}
}
@end

Now when we tap the control the background seamlessly switches to the highlighted background:

Control touch state

The last thing to do is to add some setters to change the background colours and the border. With these we can easily create a range of gradient controls:

Component showcase

The full code with examples can be found on GitHub.