UI Polish In An Instant
There are some apps that immediately make you say, “Wow! That looks really nice!” You might also assume that a “real designer” ™ was involved. In many cases, you’d be right. But it turns out that Apple has made it easy even for the non-designers among us (of which I include myself) to achieve a pretty high level of UI polish without too much effort.
Enter layers. CALayers to be exact. Every UIView has a layer; maybe more than one, because you can add layers to a view as well. There are a good handful of different types of layers, too. What can you do with layers? You can add shadow, borders, gradients, and a lot more. We’ll look at some here.
Before you begin
You will need to add the QuarzCore.framework to your project, and import the QuartzCore.h header file into your source code:
[code lang=”objc”]
// MyApp.h
#import <QuartzCore/QuartzCore.h>
[/code]
Borders
You can do quite a bit with the layer you get for free that is attached to every UIView (or UIView-subclass). You access it simply via the layer property.
On the layer, there are two properties for creating a border around your view: borderWidth and borderColor. The width is specified in pixels; usually 2 or 3 is a good value, but (for all of this) your sense of style and what you are trying to achieve should be your guide. The color value can be any color you can access via UIColor, but you must provide the value as a CGColor because under the hood everything works in terms of Core Graphics. For example:
[code lang=”objc”]
myView.layer.borderWidth = 2.0;
myView.layer.borderColor = [UIColor redColor].CGColor;
[/code]
On the left, a small view without a border, and on the right, the same view with a 2 pixel border (using the code above):
Rounded Corners
I can’t even tell you what you’d have to do manually to achieve rounded corners on an otherwise square view without layer properties! But since Apple thinks of everything, it’s easy:
To add some nice rounded corners to the above squares, we simply specify the corner radius we want:
[code lang=”objc”]
myView.layer.cornerRadius = 20.0;
[/code]
Now here’s a neat little trick you can do with corner radius: you can create a round view (button, label, whatever). The secret it to use a corner radius that is “about” half the height and width of what would otherwise be a square view. For example:
[code lang=”objc”]
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(100, 250, 40, 40); // 40px square
button.layer.cornerRadius = 20.0; // half height & width
button.layer.borderWidth = 3.0;
button.layer.borderColor = [UIColor blackColor].CGColor;
button.backgroundColor = [UIColor redColor];
[button setTitle:@"3" forState:UIControlStateNormal];
button.titleLabel.font = [UIFont boldSystemFontOfSize:22];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[/code]
Shadow
Now here’s where some real polish comes in. We can easily add a shadow to these UI elements. There are several properties for controlling shadow, including for offset, path, color, opacity, and radius. You need not set them all if you simply want a little shadow all around your object. For example:
[code lang=”objc”]
myView.layer.shadowColor = [UIColor darkGrayColor].CGColor;
myView.layer.shadowOpacity = 1.0;
myView.layer.shadowOffset = CGSizeMake(2.0, 2.0);
[/code]
The view now appears to have some depth! You can adjust the opacity from 0.0 to 1.0 for the desired level of shadow. The shadowOffset is interesting, as the values you specify for x and y indicate not only the distance from the edge of the view the shadow is placed, but also in what direction. Use negative values to move the shadow left or up, thereby changing the “direction” of the light source, e.g.:
[code lang=”objc”]
myView.layer.shaddowOffset = CGSizeMake(-2.0, 2.0);
[/code]
Gradients
Finally (for this post, at least) you can easily add a color gradient to any view using a CAGradientLayer, one of several specialized CALayers. Gradients can be added to view subclasses in their -drawRect: methods using more fundamental Core Graphics APIs, but that works only when you have a subclass and can override that method. If you’re subclassing only to be able to add a gradient in -drawRect:, think again. Using CAGradientLayer you can add a gradient to any random view without having to subclass it.
We can turn that round red button into something a little more interesting:
[code lang=”objc”]
CAGradientLayer *layer = [CAGradientLayer layer];
NSArray *colors = [NSArray arrayWithObjects:
(id)[UIColor whiteColor].CGColor,
(id)[UIColor redColor].CGColor,
nil];
[layer setColors:colors];
[layer setFrame:button.bounds];
[button.layer insertSublayer:layer atIndex:0];
button.clipToBounds = YES; // Important!
[/code]
Ok, so it may not be more interesting, but you can see the effect of the first color in the list of colors. It appears at the top of the view. We set clipToBounds to YES on the button itself so that only the portion of the gradient layer within the circle is shown. Without it, we’d see the whole gradient layer, a square.
Performance
The first time I discovered some of this (specifically, shadows) I also unknowingly introduced a big performance issue. I had applied shadow to the content view of table cells. Scrolling performance went right out the window (so to speak), until I removed the shadow effect. So, beware of adding too many effects like this in too many places, especially if you expect iOS to be drawing a lot of it, quickly.
Used judiciously, a little goes a long way when it comes to UI polish. But you can see that it’s not too hard to bring your app’s UI to the next level of “wow!”
You can improve shadow drawing performance by setting the shadowPath property of the layer. e.g…
[code lang=”objc”]
layer.shadowPath = [UIBezierPath bezierPathWithRect:layer.bounds].CGPath;
[/code]
I believe if you don’t explicitly set that it uses the alpha layer to calculate the shadow path, and it will render off screen which causes a huge performance hit.
Thanks, Ryan. (I heard from someone else via Twitter about this as well.) This is definitely an important nuance to be aware of (which I was not!).