首先要说的是CALayers 是屏幕上的一个具有可见内容的矩形区域,每个UIView都有一个根CALayer,其所有的绘制(视觉效果)都是在这个layer上进行的。(译者注:为验证这点,我写下了如下代码:
UILabel* lable = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 30)];
lable.text = @"test";
[self.view addSubview: lable];
lable.backgroundColor = [UIColor clearColor];
[lable release];
// 设定CALayer
self.view.layer.backgroundColor =[UIColor orangeColor].CGColor;
self.view.layer.cornerRadius =20.0;
self.view.layer.frame = CGRectInset(self.view.layer.frame, 20, 20);
请注意,我创建的UILable始终随着UIView的根CALayer的缩放而改变位置。)
其次,CALayer的可以影响其外观的特性有:
需要说明的是CALayer的大部分属性都可以用来实现动画效果。
另外,你可以直接使用CALayer,也可以使用其子类,如CAGradientLayer,CATextLayer, CAShapeLayer等等。
示例
首先在Xcode中创建一个View-based App,CALayer是属于QuartzCore framework的,所以需要引入QuartzCore framework,另外在程序中包括QuartzCore.h。
第一个例子是创建一个带圆角的层,在你的ViewController中的ViewDidLoad中加入下面代码:
// Import QuartzCore.h at the top of the file
#import <QuartzCore/QuartzCore.h>
// Uncomment viewDidLoad and add the following lines
self.view.layer.backgroundColor =[UIColor orangeColor].CGColor;
self.view.layer.cornerRadius =20.0;
self.view.layer.frame = CGRectInset(self.view.layer.frame, 20, 20);
结果如下:
然后添加一个带阴影效果的子层,加入下列代码:
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);
[self.view.layer addSublayer:sublayer];
效果图:
为子层增加内容(图片),你还可以设置层的边框,代码如下:
sublayer.contents =(id)[UIImage imageNamed:@"BattleMapSplashScreen.png"].CGImage;
sublayer.borderColor =[UIColor blackColor].CGColor;
sublayer.borderWidth =2.0;
效果图:
如果你希望子层也是圆角怎么办?你可能说很容易设置cornerRadius属性就行。实际上你即算是设置了cornerRadius属性,图片仍然不会显示圆角。你还需要设置masksToBounds为YES。但是这样做还是不够的,因为如果是这样,这个层的阴影显示就没有了。简单的实现方法如下(通过两个层来实现):
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.png"].CGImage;
imageLayer.masksToBounds =YES;
[sublayer addSublayer:imageLayer];
效果图:
最后,还介绍一下自绘图型的实现,其要点是要设置所绘制层的delegate。比如在我们的例子中使用ViewController作为delegate,那么就需要在ViewController中实现drawLayer:inContext方法,对层进行绘制工作。另外,还需要调用setNeedsDisplay,来通知层需要进行绘制了,于是层才会通过对delegate的drawLayer:inContext方法进行调用。
代码如下:
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);
staticconst CGPatternCallbacks callbacks ={0, &MyDrawColoredPattern, NULL};
CGContextSaveGState(context);
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetFillColorSpace(context, patternSpace);
还需要注意,radians是一个自定义函数:
static inline double radians (double degrees) { return degrees * M_PI/180; }
效果如下:
本文的完整代码下载。
http://sue602.blog.163.com/blog/static/31495307201103034736509/
If you’ve been programming for the iPhone, you’re probably really familiar with UIViews – buttons, text areas, sliders, web views, and more are all subclasses of UIView.
But you might not know much about the technology that UIView is built upon: CALayers! At least I didn’t, for quite a while.
It’s good to know a bit about CALayers, because you can use them to create some neat visual effects really easily. They’re also important to understand to work with Core Animation, which we’ll be discussing in a future tutorial.
In this tutorial, you’re going to learn the basics of using CALayers by making a simple app to create a layer and experiment with how it looks. In the process, you’ll learn what layers are, some neat properties you can set, and how to put images and custom-drawn content inside.
This tutorial assumes a basic familiarity with iPhone programming. If you are completely new, you might want to begin with the How To Create A Simple iPhone App Tutorial Series first.
So let’s get layering!
CALayers are simply classes representing a rectangle on the screen with visual content.
“But wait a darn minute,” you may say, “that’s what UIViews are for!” That’s true, but there’s a trick to that: every UIView contains a root layer that it draws to! You can access this layer (created for you by default) with following bit of code:
CALayer *myLayer = myView.layer; |
The neat thing about the CALayer class is that it contains a lot of properties that you can set on it to affect the visual appearance, such as:
You can use these properties to create some neat effects. For example, maybe you want to take an image, and put it in a white frame, and apply a shadow to it to make it look neat. Rather than whipping out Photoshop or creating a bunch of Core Graphics code, you can do this in a couple lines of code using CALayers!
The other neat thing about the properties on CALayer is that most of them are animatable. For example, you could start your image out with rounded corners, tap a button, and have it animate the corners back out to straight. This can make for some really neat effects!
You can use a CALayer on its own, or you can use one of the handy subclasses that are available, such as CAGradientLayer, CATextLayer, CAShapeLayer, and others. The default layer class for a UIView is the plain-old CALayer class, and that’s what you’ll be focusing on in this tutorial.
There’s no better way to understand using CALayers than to try them out yourself! So load up XCode, go to File\New Project, choose iOS\Application\View-based Application, and click “Choose…”. Name the project LayerFun, and click Save.
The View-based Application template starts with a single view controller, and as you know, each view controller has a root view. And as you learned above, every view has a root layer.
So now you’ll try this out for yourself by changing some properties on the view’s layer.
But first things first. To use CALayers and Core Animation, you need to use a framework that isn’t included by default in the View-based template: QuartzCore. So add it to your project by control-clicking the Frameworks group, selecting Add\Existing Framework…, and choosing QuartzCore.framework from the dropdown list.
Now that you’ve included QuartzCore, make the following changes to LayerFunViewController.m:
// Import QuartzCore.h at the top of the file #import <QuartzCore/QuartzCore.h> // Uncomment viewDidLoad and add the following lines self.view.layer.backgroundColor = [UIColor orangeColor].CGColor; self.view.layer.cornerRadius = 20.0; self.view.layer.frame = CGRectInset(self.view.layer.frame, 20, 20); |
Let’s go over this bit by bit since this is new stuff:
Compile and run your code, and you should see a rounded orange rectangle in the middle of your screen:
Just like UIViews can have subviews, CALayers can have sublayers as well. You can create a new CALayer very easily with the following line of code:
CALayer *sublayer = [CALayer layer]; |
Once you have a CALayer, you can set any properties on it you’d like. But remember there’s one property you definitely have to set: it’s frame (or bounds/position). After all, the layer needs to know how big it is (and where it’s located) in order to draw itself! When you’re done, you can add your new layer as a sublayer of another layer by the following line of code:
[myLayer addSublayer:sublayer]; |
Ok, now try this out for yourself by adding a simple sublayer to the view’s layer. Add the following lines of code inside viewDidLoad, right after where you left off last time:
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); [self.view.layer addSublayer:sublayer]; |
This creates a new layer, and sets a few properties on it – including a few you haven’t seen before to set shadows. You can see here how easy it is to set shadows on a layer – which can give some amazing effects with very little effort!
After setting the properties, it sets the frame of the layer and adds it as a sublayer to the view’s layer. Remember that these coordinates are relative to the parent layer’s frame, and since the parent layer begins at (20,20), the sublayer will be offset an additional (30,30) from that, so on the screen it will be at (50,50).
Compile and run your code, and you should now see a blue sublayer on the screen!
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!
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!
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:
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.
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! :]