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
Adding control in Interface Builder

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

Rendered 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
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
Component showcase

The full code with examples can be found on GitHub.