iOS Brownbag: View vs. Layers (including Clock Demo)
For many iOS developers, layers are a lower-level, complex version of the UIView. In reality, it’s the UIView which is a thin layer on top of CALayer. Unfortuntely, 95% of the iOS books and documentation out there talk almost exclusively about the UIKit and so it’s no surprise we get that impression.
In this blog, I’m going to explain the difference between UIView and CALayer, tell you when you should be using either, and demonstrate the power of layers by creating a working clock.
UIView vs. CALayer
Despite what many developers think, it’s the CALayer that’s the fundamental drawing unit in iOS. The reason why we perceive UIView as such is because it’s a thin layer on top of CALayer and for most UI challenges using some form of UIView works just fine. You can create custom views, draw into them, handle user interactions, and even animate them without ever having to touch CALayer.
Every UIView comes packaged with a CALayer knows as the “backing layer” or “underlying layer.” Many of the methods you call on UIView simply delegate to the layer. When you change a view’s frame, it’s simply changing the layer’s frame. If you change the alpha, it changes the layer’s alpha…and so on with background colors, transformations and more. And while you can maintain a hierarchy of UIViews each representing parents and children of one another, you can do the same with CALayer.
Now, let’s say you wanted to write a Bar Chart component. You’d subclass UIView (or better yet, UIControl) and write the logic for displaying the colored bars you need to represent your data. If you’re comfortable with the UIKit, you might find it an easy choice to use subviews to represent each bar. You can use a plain UIView, slap a background color on it, size it…and repeat a dozen times depending on how much data you’re representing. But, you’d be wrong. You shouldn’t be using subviews, you should be using sublayers.
Should I use subviews or sublayers?
This isn’t a simple question, but there’s one rule of thumb that make deciding a lot easier. The main different between a view and a layer is that views can accept user input while a layer cannot. A layer is simply a graphical representation. Views can handle user taps, drags, pinches, etc. To me, this is all you need to know.
So, when making your bar chart, you should be using sublayers for the bars. Why? Because they don’t need user input and since they’re lighter weight than views, they’re the right choice. So what do you gain by using layers? Well, aside from performance, you also gain implicit animation and better control of the visual representation. You see, every property you change on a layer is automatically animated. You can disable them if you need to, but they come in handy. Also, you can apply shadow effects, corner radiuses, 3D transforms and more on layers. You can apply many of these on views, too, but they’re simply delegating to their “underlying layer” and in many cases stealing event notifications you’d rather they not from your view. :-)
Demo: Let’s build a working clock!
To demonstrate how we use layers, I’m going to build a working clock. The architecture will be as follows:
- ClockView will extend UIControl so that we can use it with Interface Builder and, potentially in the future, handle user events.
- All the parts of the clock will be layers.
- Since all of the parts are layers, we won’t be implementing drawRect.
- A timer, that we can start and stop, will handle updating the clock by adjusting the three hands.
Here’s what our clock looks like:
Every piece of it is rendered in layers: the face, numbers, ticks, all three hands, and the centerpoint. I used a mix of techniques to render it. Shadow effects give the hands depth, rounded line miters give the hands softness, and combining anchoring with transforms gives the hands a more natural clock look by having them rotate around the centerpoint. No PNGs were used and no primitives were drawn.
Here’s the source code. You can take this code and drop it right into your own project. Simply call ClockView#startUpdates to get things going! Also, note that I only implement initWithCoder, so if you want to add it to your UI programmatically, override initWithFrame as well. You’ll also need to link in the QuartzCore framework — anything that uses CA* will need it.
(NOTE: It’s not a memory leak, it’s ARC! Also, I recommend using constants instead of raw time constants. Why not try iBoost?)
On line #41, I use a CAShapeLayer. These things are awesome! By providing a path, you can make a layer of any shape. You can specify stroke and fill colors, change line attributes, and so on. The clock would be way more complex without these bad boys.
On line #45, I use the position property to place the face layer. Layers are positioned differently than views. While you might use frame, bounds, or center to position a view…you use bounds, position, and anchorPoint to position a layer. While bounds works as expected, position and anchorPoint are different. The anchorPoint is a CGPoint where the X and Y normally range between 0 and 1. The 0 represents the top or left of the image, and 1 is the bottom or right of the image. The default is 0.5, 0.5 which is the center of the image. That means that whatever you set the position property to, that’s where the center of the layer will be. You can change the anchorPoint to position things differently. If you set it to 0, 0, position will be where the top left of the image is. Get it? Additionally, you can make an anchor 0.5, 10 which will position the layer 10X the height of the layer above the position. Magic.
On line #59, I use a CATextLayer which lets you specify text easily as a layer.
On line #67, I use 3D transforms to position the numbers. By making the bounds of the number higher than expected, a simple rotational transform positions the numbers where I want. (If you wanted the numbers all “face up”, you’d have to position these using trigonometry instead, but this is a layer demo!) Note that the last 3 params of this method specify which axes to rotate around. I specify the last one, which is the z-axis, which is by far the most common.
Layers are the powerful core of the Cocoa Touch framework. Even if you’ve never used a layer directly, you’re probably familiar with many concepts because UIViews are simply thin layers on top of them. Want to give your view a rounded rectangle appearance? Simply set myView.layer.cornerRadius to 5.0! Want dynamic drop shadows to make image sizing easier? Set myView.layer.shadowOpacity greater than 0.0. DONE! Remember, UIViews are designed to handle user input, and layers are the graphical workhorse. Use them!