Monday, January 3, 2011

Rendering views using CALayer, Part 1

Rendering views using CALayer, Part 1

13 Nov
For myDrumPad the main pad buttons are images. I create a UIButton object, and use setBackgroundImage:forState: to customize which image will be used for each state (UIControlStateNormal and UIControlStateHighlighted mainly).  I customize the title label font, shadow and color, and voilĂ  I have a pad button that simulates the look and feel of  a Korg padKONTROL. There’s just a few small problems with it.
  1. The images on the iPad are fairly large, and memory is at a premium.
  2. The size of these buttons can change in portrait vs. landscape. It’s time-consuming to export different versions from Photoshop for the different orientations.
  3. The buttons are sized differently depending on the size of the button grid (e.g. a 3×3 grid of buttons have larger images than 4×4 or 5×5 grids). If I resize these images on-the-fly, then the edges look blurred and aren’t well-defined.
  4. The retina display complicates all of this, meaning I have to have two versions of each image.
  5. I want to be able to customize the buttons to have different colors when you’re on different drum sets.
Because of that long list, simply using an image isn’t good enough. But instead of drawing my images using regular Core Graphics drawing routines, I’m going to use Core Animation Layers, or CALayers, to accomplish the same thing. Ultimately I want my buttons to be able to be animated, to change color, and to feel more “alive” than a static image could accomplish.

Before you jump straight to the code…

Before you go any farther, I highly suggest you download the WWDC 2010 videos, and watch the sessions on Core Animation. There’s a lot of great information there and the presenters go to great lengths to show how flexible Core Animation is. The summary though is that CALayer objects can be stacked and nested just like layers in Photoshop, and actually form the foundation for how UIViews interact. The interesting thing about Core Animation is that when a parent layer is animated, moved, or in any other way interacted with, the sub-layers come along for the ride.
Most people focus on Core Animation as a way of, duh, animating their UI. While this is the really powerful capability that Core Animation exposes, don’t be confused by its name. Using its simpler capabilities can give you quite a lot, even without the animation capabilities. Think of it as going to Disney Land; yes there are plenty of amazing rides that cost millions of dollars and years of engineering to produce, but you can still have a fantastic time riding the Teacups. Core Animation provides some remarkable tools to do high-speed sophisticated path and keyframe animations, but layers, borders, shadows and gradient layers are still very effective tools.

Starting with your view

Typically when you’re creating your own custom view, you’ll start by subclassing UIView. This is what I’ll be doing here as well, but one of the things many people don’t realize is that with every instance of a UIView object, you get a CALayer for free. The layer property contains a default backing-store that your view will render itself to. At this point you can either draw directly to the layer using Core Graphics drawing operations, or you can create additional sub-layers for your view.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super initWithCoder:aDecoder])) {
        [self setupLayers];
    }
    return self;
}
- (void)setupLayers {
    self.layer.cornerRadius = 10.0;
    self.layer.borderColor = [UIColor redColor].CGColor;
    self.layer.borderWidth = 1.0f;
    self.layer.shadowOffset = CGSizeMake(0.0f, 2.0f);
    self.layer.shadowOpacity = 1.5f;
    self.layer.shadowColor = [UIColor blackColor].CGColor;
    self.layer.shadowRadius = 2.5f;
    self.layer.anchorPoint = CGPointMake(0.5f, 0.5f);
}

This makes your view look something like the screenshot you see here. You’ll notice that, since our layer is empty except for the border, the shadow extends into the view itself. So, lets give our layer some content to display. The easiest way is to simply assign an image to the content property, but I want to be a bit more creative than that.
Instead, I’ll create a gradient inside this view. Add the following code to the end of the setupLayers method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.anchorPoint = CGPointMake(0.0f, 0.0f);
gradient.position = CGPointMake(0.0f, 0.0f);
gradient.bounds = self.layer.bounds;
gradient.cornerRadius = 10.0;
gradient.colors = [NSArray arrayWithObjects:
                   (id)[UIColor colorWithRed:0.82f
                                       green:0.82f
                                        blue:0.82f
                                       alpha:1.0].CGColor,
                   (id)[UIColor colorWithRed:0.52f
                                       green:0.52f
                                        blue:0.52f
                                       alpha:1.0].CGColor,
                   nil];
[self.layer addSublayer:gradient];

This gives you something along these lines instead. While this is starting out quite simply, you can see how simple interfaces can be drawn by using declarative routines as opposed to using regular images, or even procedural calls using Core Graphics.
As I create more and more sophisticated custom views using CALayer, I’ll add more to my blog on the subject. Before I go, I’ll show a screenshot of what I’ve managed to accomplish for my new buttons.

No comments:

Post a Comment