CALayers Tutorial for iOS: Introduction to CALayers
A CALayers tutorial that shows you how you can add drop shadows, rounded corners, and other neat effects to views in your iOS app. By Ray Wenderlich.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
CALayers Tutorial for iOS: Introduction to CALayers
15 mins
Setting CALayer Image Content
One of the coolest things about CALayers is that they can contain more than just plain colors. It’s extremely easy to have them contain images instead, for example.
So let’s replace the blue sublayer with an image instead. You can take a Default.jpg from one of your iPhone apps to use as a test, or you can download a splash screen from one of my apps.
Add the splash screen to your project, and inside viewDidLoad right before the last line you added that adds the sublayer to self.view.layer, add the following lines of code:
sublayer.contents = (id) [UIImage imageNamed:@"BattleMapSplashScreen.jpg"].CGImage;
sublayer.borderColor = [UIColor blackColor].CGColor;
sublayer.borderWidth = 2.0;
This sets the contents of the layer to an image (that’s literally all it takes!) and also sets the borderColor and borderWidth to set up a black stroke around the edges, to demonstrate how that works.
Compile and run your code, and you should now see the blue layer’s contents replaced with your splash screen image!
A Note about Corner Radius and Image Content
Now that you have this working, you might think it would be cool to round the corners of the splash screen as well, by setting the cornerRadius.
Well the problem is that as far as I can tell, if you set that on a CALayer with image contents, the image will still be drawn outside the corner radius boundary. You can solve this by setting sublayer.masksToBounds to YES, but if you do that the shadows won’t show up because they’ll be masked out!
I found a workaround by creating two layers. The outer layer is just a colored CALayer with a border and a shadow. The inner layer contains the image, and is also rounded but set up to mask. That way, the outer layer can draw the shadow, and the inner layer can contain the image.
Try this out by modifying the code to create the sublayer as follows:
CALayer *sublayer = [CALayer layer];
sublayer.backgroundColor = [UIColor blueColor].CGColor;
sublayer.shadowOffset = CGSizeMake(0, 3);
sublayer.shadowRadius = 5.0;
sublayer.shadowColor = [UIColor blackColor].CGColor;
sublayer.shadowOpacity = 0.8;
sublayer.frame = CGRectMake(30, 30, 128, 192);
sublayer.borderColor = [UIColor blackColor].CGColor;
sublayer.borderWidth = 2.0;
sublayer.cornerRadius = 10.0;
[self.view.layer addSublayer:sublayer];
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = sublayer.bounds;
imageLayer.cornerRadius = 10.0;
imageLayer.contents = (id) [UIImage imageNamed:@"BattleMapSplashScreen.jpg"].CGImage;
imageLayer.masksToBounds = YES;
[sublayer addSublayer:imageLayer];
Compile and run your code, and now your image will have rounded corners!
CALayer and Custom Drawn Content
If you want to custom-draw the contents of a layer with Core Graphics instead of putting an image inside, that is quite easy too.
The idea is you set a class as the delegate of the layer, and that class needs to implement a method named drawLayer:inContext. This can contain Core Graphics drawing code to draw anything you want in that space.
Let’s try this out by adding a new layer, and drawing a pattern inside it. You’ll set the view controller as the delegate of the layer, and implement the drawLayer:inContext method to draw the pattern. As for the pattern drawing code, you’ll be using the same code as in the Core Graphics 101: Patterns tutorial posted earlier on this site.
Add the following code at the bottom of your viewDidLoad to add a new layer that will be custom drawn:
CALayer *customDrawn = [CALayer layer];
customDrawn.delegate = self;
customDrawn.backgroundColor = [UIColor greenColor].CGColor;
customDrawn.frame = CGRectMake(30, 250, 128, 40);
customDrawn.shadowOffset = CGSizeMake(0, 3);
customDrawn.shadowRadius = 5.0;
customDrawn.shadowColor = [UIColor blackColor].CGColor;
customDrawn.shadowOpacity = 0.8;
customDrawn.cornerRadius = 10.0;
customDrawn.borderColor = [UIColor blackColor].CGColor;
customDrawn.borderWidth = 2.0;
customDrawn.masksToBounds = YES;
[self.view.layer addSublayer:customDrawn];
[customDrawn setNeedsDisplay];
Most of the code here you’ve seen before (creating a layer, setting properties such as shadow/cornerRadius/border/masksToBounds), however there are two new pieces:
- First, it sets the delegate of the layer to self. This means that this object (self) will need to implement the drawLayer:inContext method to draw the contents of the layer.
- Second, after it adds the layer, it needs to tell the layer to refresh itself (and call drawLayer:inContext) by calling setNeedsDisplay. If you forget to call this, drawLayer:inContext will never be called, so the pattern won’t show up!
Next add the code to implement drawLayer:inContext, as shown below:
void MyDrawColoredPattern (void *info, CGContextRef context) {
CGColorRef dotColor = [UIColor colorWithHue:0 saturation:0 brightness:0.07 alpha:1.0].CGColor;
CGColorRef shadowColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.1].CGColor;
CGContextSetFillColorWithColor(context, dotColor);
CGContextSetShadowWithColor(context, CGSizeMake(0, 1), 1, shadowColor);
CGContextAddArc(context, 3, 3, 4, 0, radians(360), 0);
CGContextFillPath(context);
CGContextAddArc(context, 16, 16, 4, 0, radians(360), 0);
CGContextFillPath(context);
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context {
CGColorRef bgColor = [UIColor colorWithHue:0.6 saturation:1.0 brightness:1.0 alpha:1.0].CGColor;
CGContextSetFillColorWithColor(context, bgColor);
CGContextFillRect(context, layer.bounds);
static const CGPatternCallbacks callbacks = { 0, &MyDrawColoredPattern, NULL };
CGContextSaveGState(context);
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetFillColorSpace(context, patternSpace);
CGColorSpaceRelease(patternSpace);
CGPatternRef pattern = CGPatternCreate(NULL,
layer.bounds,
CGAffineTransformIdentity,
24,
24,
kCGPatternTilingConstantSpacing,
true,
&callbacks);
CGFloat alpha = 1.0;
CGContextSetFillPattern(context, pattern, &alpha);
CGPatternRelease(pattern);
CGContextFillRect(context, layer.bounds);
CGContextRestoreGState(context);
}
This code is literally copied and pasted from the Core Graphics 101: Patterns tutorial (just with a different color, and using the passed in context and layer bounds), so we aren’t going to cover it again here. If you want to know what it does, refer to the above tutorial.
That’s it! Compile and run the code, and you should now see a blue grip pattern below the image.
Where To Go From Here?
Here is a sample project with all of the code we’ve developed in the above tutorial.
At this point, you should be familiar with the concepts of CALayers and how to create some neat effects with them really easily. In future tutorials, I’ll show you how you can animate CALayers using Core Animation, and use some really handy subclasses of CALayer such as CAGradientLayer, CATextLayer, and CAShapeLayer.
In the meantime, check out the Core Animation Programming Guide, which does a great job talking about Core Animation, and more details about CALayers.
If you’ve found some good ways to use CALayers in your projects, please share below! :]