CALayer图层简介

一.简介:

CALayer类在概念上和UIView类似,同样也是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。它们有一些方法和属性用来做动画和变换。和UIView最大的不同是CALayer不处理用户的交互。

CALayer只是UIView 的一个基本架构,layer使绘制和动画视图内容和保持高帧速率的内容变得更加容易和高效。layer上不具备处理事件,绘制内容,响应链(responder chain)等

Layer也和View一样存在着一个层级树状结构,称之为图层树(Layer Tree),直接创建的或者通过UIView获得的(view.layer)用于显示的图层树,称之为模型树(Model Tree),模型树的背后还存在两份图层树的拷贝,一个是呈现树(Presentation Tree),一个是渲染树(Render Tree). 呈现树可以通过普通layer(其实就是模型树)的layer.presentationLayer获得,而模型树则可以通过modelLayer属性获得.模型树的属性在其被修改的时候就变成了新的值,这个是可以用代码直接操控的部分;呈现树的属性值和动画运行过程中界面上看到的是一致的.而渲染树是私有的,你无法访问到,渲染树是对呈现树的数据进行渲染,为了不阻塞主线程,渲染的过程是在单独的进程或线程中进行的,所以你会发现Animation的动画并不会阻塞主线程.

呈现树通过图层树中所有图层的呈现图层所形成。注意呈现图层仅仅当图层首次被提交(就是首次第一次在屏幕上显示)的时候创建,所以在那之前调用-presentationLayer将会返回nil。

layer.presentationLayer的运用,如果你要在一个运动的layer中判断,一个点是否在layer中,如果直接用layer可能出现,时间在现实中是在layer上,但是判断上并没有,因为这个时候layer的几何属性位置是和layer的位置有差别的,应该用layer.presentationLayer来判断。

具备的功能:
阴影,圆角,带颜色的边框
3D变换
非矩形范围
透明遮罩
多级非线性动画

二.CALayer使用

1.基本方法

1.1 初始化的3种方法

/*
 + (instancetype)layer;
 - (instancetype)init;
 - (instancetype)initWithLayer:(id)layer; 复制一个图层
 */
CALayer *layer = [CALayer layer];
// 设置layer的大小
layer.bounds = CGRectMake(0, 0, 100, 100);
// 设置位置居中
layer.position = self.view.center;
// 添加视图
[self.view.layer addSublayer:layer];
// 这里直接设置了一个图片到图层
UIImage *image2 = [UIImage imageNamed:@"2.jpg"];
layer.contents = (__bridge id _Nullable)(image2.CGImage);

效果图

CALayer创建.jpg

1.2 层的添加,移除,插入,替换

 sublayers:子layers
 superlayer:父layer
 
 - (void)addSublayer:(CALayer *)layer;

 - (void)insertSublayer:(CALayer *)layer atIndex:(unsigned)idx;

 - (void)insertSublayer:(CALayer *)layer below:(nullable CALayer *)sibling;

 - (void)insertSublayer:(CALayer *)layer above:(nullable CALayer *)sibling;

 - (void)replaceSublayer:(CALayer *)layer with:(CALayer *)layer2;

1.3相关层对象

呈现树通过图层树中所有图层的呈现图层所形成。注意呈现图层仅仅当图层首次被提交(就是首次第一次在屏幕上显示)的时候创建,所以在那之前调用-presentationLayer将会返回nil。

 - (nullable instancetype)presentationLayer; 得到层的副本。得到的是当前层的副本,也就是说在动画的时候presentationLayer是不断改变的。当屏幕没有开始显示layer的时候,为nil

 - (instancetype)modelLayer; 返回模型层。如果是-presentationLayer返回的呈现层调用则返回所依赖的CALayer;如果不是-presentationLayer返回的层调用则返回self。只有当涉及到表示层的更改时,该方法才会返回一个值。如果没有正在进行的事务,调用此方法的结果是未定义的。

1.4 渲染

 当更新层,改变不能立即显示在屏幕上。当所有的层都准备好时,可以调用setNeedsDisplay方法来重绘显示。
 [layer setNeedsDisplay];
 
 若要重绘部分屏幕区域,请使用setNeedsDisplayInRect:方法,通过在CGRect结构的区域更新:
 [layer setNeedsDisplayInRect:CGRectMake(0.0,0.0,50.0,75.0)];
 
 如果是用的Core Graphics框架来执行渲染的话,可以直接渲染Core Graphics的内容。用renderInContext:来做这个事。
 [layer renderInContext:UIGraphicsGetCurrentContext()];
 
 当它的边界矩形变化时,层内容是否必须更新
 layer.needsDisplayOnBoundsChange = NO;
 
 正常更新周期之外强制更新您的层的内容,一般不使用。首选方法是调用setNeedsDisplay,让系统在下一次周期中更新层
 [layer displayIfNeeded];
 
 指示该层是否被标记为需要更新
 [layer needsDisplay];
 
 返回一个布尔值,表示对指定键的更改是否要求重新显示该层。
 BOOL neetd_display = [CALayer needsDisplayForKey:@"position"];
 NSLog(@"%d", neetd_display);

1.5 动画的添加,移除

 - (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;
 - (void)removeAllAnimations;
 - (void)removeAnimationForKey:(NSString *)key;
 - (nullable NSArray<NSString *> *)animationKeys;
 - (nullable CAAnimation *)animationForKey:(NSString *)key;

1.6 层调整和布局

 指示层的sublayers的布局已经更改并且必须更新,当该layer的边界发生变化,系统通常会自动调用该方法。
 - (void)setNeedsLayout;
 
 子类可以覆盖此方法来给sublayer布局,详情可以看9。
 - (void)layoutSublayers;
 
 遍历层的超级层,直到找到不需要布局的父层,然后在祖先的整个层上执行布局。
 - (void)layoutIfNeeded;
 
 返回一个布尔值,指示该层是否被标记为需要一个布局更新。
 - (BOOL)needsLayout;
 
 最适合的尺寸
 - (CGSize)preferredFrameSize;

1.7 Scrolling

 visibleRect - 返回layer在屏幕显示的内容的位置
 
 滚动到某一点,只有CAScrollLayer有效
 - (void)scrollPoint:(CGPoint)p;
 
 滚动到某一个区域可见,只有CAScrollLayer有效
 - (void)scrollRectToVisible:(CGRect)r;

1.8 时间转换方法:用来得到layer之间通过speed,timeOffset等参数因数在内的时间转换后的时间
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;
2.contents属性

contents给CALayer赋予内容,类型为id类型,意味着它可以是任何类型的对象。但是在ios中你只能把CGImageRef赋值给它,如果不是CGImage得到的内容会是空。赋值方法和效果可以参考上面1的例子。
这里主要讲如何给layer赋内容,主要有3种方法:

a.图像对象直接赋给层对象的内容属性(这种技术最适合用于不需要或很少更改的层内容。)
b.使用CALayerDelegate委托绘制层的内容。(此技术最适合于可能周期性变化的层内容,可以由外部对象提供,例如view)
c.定义一个layer子类,并覆盖它的绘图方法之一,以自己提供layer内容。(如果你需要创建一个自定义layer子类,或者你想改变层的基本绘图行为,这个技巧是合适的。)

2.1第一种方法

UIImage *image = [UIImage imageNamed:@"123.jpg"];
self.view.layer.contents = (__bridge id _Nullable)(image.CGImage);

2.2第二种方法

// 说明:对于UIView应该使用drawRect:来绘图,不要使用这个代理。UIView自动使它自己成为layer的委托,并实现所需的委托方法
CALayer *layer = [CALayer layer];
layer.bounds = CGRectMake(0, 0, 100, 100);
layer.position = self.view.center;
[self.view.layer addSublayer:layer];

layer.delegate = self;

// 告诉layer需要更新显示,不适用这个不会调用委托的方法
[layer setNeedsDisplay];
#pragma mark - contents属性方法2 <CALayerDelegate>

// CALayerDelegate必须实现displayLayer:或drawLayer:inContext:方法。如果CALayerDelegate实现了displayLayer和drawLayer:inContext:方法,层只调用displayLayer:方法。

// 该实现负责创建位图并将其分配到该层的内容属性
- (void)displayLayer:(CALayer *)layer {
    
    UIImage *image = [UIImage imageNamed:@"2.jpg"];
    layer.contents = (__bridge id _Nullable)(image.CGImage);
}

// 创建一个图形上下文来绘制位图,然后调用你的委托方法来填充位图
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    
    CGMutablePathRef thePath = CGPathCreateMutable();
    
    CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
    CGPathAddCurveToPoint(thePath,
                          NULL,
                          15.f,250.0f,
                          295.0f,250.0f,
                          295.0f,15.0f);
    
    CGContextBeginPath(ctx);
    CGContextAddPath(ctx, thePath);
    
    CGContextSetLineWidth(ctx, 5);
    CGContextStrokePath(ctx);
    
    // Release the path
    CFRelease(thePath);
}

2.3第三种方法

#import "CACustomLayer.h"

@implementation CACustomLayer


// 第一种
// 第一种实现了不会调用第二种
- (void)display {

    UIImage *image = [UIImage imageNamed:@"2.jpg"];
    self.contents = (__bridge id _Nullable)(image.CGImage);
}

// 第二种
- (void)drawInContext:(CGContextRef)ctx {
    
    CGMutablePathRef thePath = CGPathCreateMutable();
    
    CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
    CGPathAddCurveToPoint(thePath,
                          NULL,
                          15.f,250.0f,
                          295.0f,250.0f,
                          295.0f,15.0f);
    
    CGContextBeginPath(ctx);
    CGContextAddPath(ctx, thePath);
    
    CGContextSetLineWidth(ctx, 5);
    CGContextStrokePath(ctx);
    
    // Release the path
    CFRelease(thePath);
    
}
@end
CACustomLayer *layer = [[CACustomLayer alloc] init];
layer.bounds = CGRectMake(0, 0, 100, 100);
layer.position = CGPointMake(CGRectGetMidX(self.view.frame), CGRectGetMidY(self.view.frame));
[self.view.layer addSublayer:layer];

// 告诉layer需要更新显示,不使用这个不会layer重写的方法
[layer setNeedsDisplay];

3.CALayer的几何学
这里我使用同一个layer的介绍下面的内容

// 创建一个layer
UIImage *background_image = [UIImage imageNamed:@"123.jpg"];
self.view.layer.contents = (__bridge id)background_image.CGImage;

CALayer *layer = [CALayer layer];
UIImage *image = [UIImage imageNamed:@"2.jpg"];
layer.contents = (__bridge id)image.CGImage;
[self.view.layer addSublayer:layer];

3.1 CALayer布局

// frame 和view中的frame类似,表示在父layer里面的位置
// bounds 和view中的bounds类似,表示自己的大小
// position 和view中的center类似,表示在父layer里的中心位置
// 注:这些赋值参数尽量使用整数
layer.bounds = CGRectMake(0, 0, 100, 100);
layer.position = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
// 这里设置frame和设置bounds,position一致
// layer.frame = CGRectMake(CGRectGetMidX(self.view.bounds) - 50, CGRectGetMidY(self.view.bounds) - 50, 100, 100);

3.2 锚点anchorPoint

这种瞄点可以改变视图的位置,默认值是[0.5,0.5],如果设置为[0.0,0.0],视图就会往右下移,显示的开始位置变成了原先显示的中点位置。简单的说,position,bounds,anchorPoint决定了视图的frame。
锚点在ios中和mac os 一点不同,了解具体的内容可以自行查阅资料,也可以看官方文档

锚点anchorPoint.png

例子:

// 设置layer的位置在父layer中心让左上分别偏移50的位置
layer.position = CGPointMake(CGRectGetMidX(self.view.bounds) - 50, CGRectGetMidY(self.view.bounds) - 50);
// 这里设置layer又显示到了中心位置。
layer.anchorPoint = CGPointMake(0.0, 0.0);

3.3 不同坐标系之间的图层转换方法

- (CGPoint)convertPoint:(CGPoint)p fromLayer:(nullable CALayer *)l;
- (CGPoint)convertPoint:(CGPoint)p toLayer:(nullable CALayer *)l;
- (CGRect)convertRect:(CGRect)r fromLayer:(nullable CALayer *)l;
- (CGRect)convertRect:(CGRect)r toLayer:(nullable CALayer *)l;

调用的layer和l必须共享同一个父layer。
l可以为nil,如果您为l参数指定nil,这个方法将返回从该层的原点减去的原始点。

例子:

CALayer *layer2 = [CALayer layer];
layer2.backgroundColor = [UIColor yellowColor].CGColor;
layer2.bounds = CGRectMake(0, 0, 100, 100);
layer2.position = CGPointMake(CGRectGetMidX(self.view.bounds) + 50, CGRectGetMidY(self.view.bounds) + 50);
[self.view.layer insertSublayer:layer2 below:layer];

// 得到layer的CGPointMake(0, 0)在屏幕显示点,在_displayLayer.layer2为坐标参考对应的坐标
CGPoint point1 = [layer convertPoint:CGPointMake(0, 0) fromLayer:layer2];
NSLog(@"%@", NSStringFromCGPoint(point1));

CGPoint point2 = [layer2 convertPoint:CGPointMake(0, 0) toLayer:layer];
NSLog(@"%@", NSStringFromCGPoint(point2));

// 得到self.view.layer的CGRectMake(0, 0, 1, 1)在屏幕显示点,在layer为坐标参考对应的坐标
NSLog(@"%@", NSStringFromCGRect(layer.frame));
CGRect rect1 = [layer convertRect:CGRectMake(0, 0, 1, 1) fromLayer:self.view.layer];
NSLog(@"%@", NSStringFromCGRect(rect1));

CGRect rect2 = [self.view.layer convertRect:CGRectMake(0, 0, 1, 1) toLayer:layer];
NSLog(@"%@", NSStringFromCGRect(rect2));

3.4 hitTest

返回p点,在层层次结构最底层的layer
- (CALayer *)hitTest:(CGPoint)p;
返回是否包含指定的点。
- (BOOL)containsPoint:(CGPoint)p;
3.5 内容垂直翻转。不影响内容,只影响layer的几何属性。

layer.geometryFlipped = NO;

4.显示效果调整属性

新建一个layer来说明:

// 创建一个layer
UIImage *background_image = [UIImage imageNamed:@"123.jpg"];
self.view.layer.contents = (__bridge id)background_image.CGImage;

CALayer *layer = [CALayer layer];
UIImage *image = [UIImage imageNamed:@"2.jpg"];
layer.contents = (__bridge id)image.CGImage;
layer.bounds = CGRectMake(0, 0, 100, 100);
layer.position = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
[self.view.layer addSublayer:layer];

4.1 圆角和边界裁剪 cornerRadius masksToBounds

// 设置圆角为宽度的一半
// 边界裁剪默认为no,所有设置圆角需要边界裁剪设置为yes
layer.cornerRadius = 50;
layer.masksToBounds = YES;

4.2 背景颜色

layer.backgroundColor = [UIColor whiteColor].CGColor;

4.3 边框, 边框的设置在设置圆角的时候,会受圆角影响

// 边框颜色
layer.borderColor = [UIColor orangeColor].CGColor;
// 边框宽度
layer.borderWidth = 1;

效果图:

边框设置效果图.jpg

4.4 阴影效果

shadowOpacity 控制阴影的透明度,默认为0,完全透明
shadowColor 控制阴影的颜色,默认是黑色
shadowOffset 控制阴影的方向和距离,它是一个CGSize的值,宽度控制这阴影横向的位移,高度控制着纵向的位移,默认值是 {0, -3},意即阴影相对于Y轴有3个点的向上位移(主要是mac os 里面的坐标系统,所以默认是向上的,ios中,自己设置就可以了)。
shadowPath 阴影效果显示,可以由UIBezierPath来控制路径,比如设置一个圆的阴影效果
由于阴影效果是在layer外显示的,所以masksToBounds为yes的时候,会被裁剪,不会显示。解决这种方法通常在外面套一个layer实现阴影效果

// 创建一个显示阴影的layer
CALayer *shadowLayer = [CALayer layer];
shadowLayer.bounds = CGRectMake(0, 0, 100, 100);
shadowLayer.position = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
[self.view.layer addSublayer:layer];
// 设置阴影效果
shadowLayer.shadowOpacity = 0.6;
shadowLayer.shadowColor = [UIColor orangeColor].CGColor;
shadowLayer.shadowOffset = CGSizeMake(0, 0);
// 添加shadowLayer在layer层的下面
[self.view.layer insertSublayer:shadowLayer below:layer];

// 因为layer被裁剪为圆形,所有阴影效果要保持一直,通过shadowPath来设置。
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 100, 100) cornerRadius:50];
shadowLayer.shadowPath = bezierPath.CGPath;

效果图:

设置一个圆形layer的阴影效果图.jpg

4.5 图层蒙版

mask属性就像是一个饼干切割机,mask图层实心的部分会被保留下来,其他的则会被抛弃

UIImage *maskImage = [UIImage imageNamed:@"t"];
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = layer.bounds;
maskLayer.contents = (__bridge id _Nullable)(maskImage.CGImage);
layer.mask =  maskLayer;
// 为了显示效果暂时把shadowLayer隐藏
shadowLayer.hidden = YES;

//    // 去除蒙版,回复原先的显示效果
//    layer.mask = nil;
//    shadowLayer.hidden = NO;

效果图:

图层蒙版效果图.jpg

4.6 组透明度
类似于UIView的alpha,layer也有一个opacity属性来设置layer的透明度。
在layer设置opacity的时候,会影响子layer的透明,这个主要是由allowsGroupOpacity组不透明度来控制,这个值取决于Info.plist设置,在ios7之后默认为yes,就是说layer设置了opacity会整合子layer的透明度,看起来他们就是一个试图,透明度一样。当然如果设置为no的时候,会产生子试图的透明度和layer不一致。

调整layer:

// 在layer上,添加一个黑色的subLayer
CALayer *subLayer = [CALayer layer];
subLayer.backgroundColor = [UIColor blackColor].CGColor;
subLayer.frame = CGRectMake(20, 20, 60, 60);
[layer addSublayer:subLayer];

// 为了体现出区别,我这里设置视图往下移
layer.position = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds) + 200);
// 隐藏shadowLayer
shadowLayer.hidden = YES;
设置不同的值得效果图.png

总结:
1.设置layer的opacity会对subLayer产生影响
2.设置subLayer的opacity会在layer参数产生影响的基础上再产生影响。
3.allowsGroupOpacity决定了subLayer能对layer的opacity参生不同的效果,layer的透视效果㛑发送了改变。
4.为了不要对比得复杂,我这里没有列举出全部的例子,权权是启到一个知识回顾提示作用。

还可以设置CALayer的一个叫做shouldRasterize属性来实现组透明的效果,如果它被设置为YES,在应用透明度之前,图层及其子图层都会被整合成一个整体的图片,这样就没有透明度混合的问题了
如果你使用了shouldRasterize,那么需要通过rasterizationScale属性去匹配屏幕,以防止出现Retina屏幕像素化的问题
注:当shouldRasterize和UIViewGroupOpacity一起的时候,会出现性能问题,这个在图层性能上会提到。

// layer.shouldRasterize = YES;
// layer.rasterizationScale = 2;

4.7 显示和隐藏

layer.hidden = NO;

4.8 可以通过style属性给属性赋值,如果已经给layer属性赋过值,那么给这个属性设置将不起作用。这个属性一般在对进行自定义属性,添加隐式动画的时候使用

NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setObject:(__bridge id)[UIColor redColor].CGColor forKey:@"borderColor"];
[dic setObject:@2 forKey:@"borderWidth"];
layer.style = dic;

4.9 allowsEdgeAntialiasing 允许边缘抗锯齿

4.10 edgeAntialiasingMask 使用此属性禁用对其他层边缘的反锯齿,以消除可能发生的边界

4.11 和contents内容设置有关的参数

// contentsGravity 内容布局方式
layer.contentsGravity = kCAGravityResizeAspectFill;

// contentsScale 内容倍数
layer.contentsScale = [UIScreen mainScreen].scale;

// contentsRect 属性允许我们在图层边框里显示寄宿图的一个子域, 比如:把图片按中心分成4个部分,只显示图片的后下半部分[0.5, 0.5, 1, 1],前上部分[0.0, 0.0, 0.5, 0.5];
layer.contentsRect = CGRectMake(0.0, 0.0, 1, 1);

// contentsCenter内容中心,可以配合contentsGravity进行内容显示调整,他工作起来的效果和UIImage里的-resizableImageWithCapInsets: 方法效果非常类似。简单的说,设置这个会改变图片的拉伸效果,默认情况下,contentsCenter是{0, 0, 1, 1},这意味着如果大小(由conttensGravity决定)改变了,那么寄宿图将会均匀地拉伸开。但是如果我们增加原点的值并减小尺寸{0.25, 0.25, 0.5, 0.5},就只会在这个部分就行拉伸。
layer.contentsCenter = CGRectMake(0.0, 0.0, 1.0, 1.0);

// contentsAreFlipped 显示的内容是否翻转

// opaque 内容是否不透明,只影响由核心动画管理的备份存储。如果内容不包含alpha通道,设置为yes,可以提高性能。
layer.opaque = NO;

// 是否在后台绘制内容
layer.drawsAsynchronously = NO;

// contentsFormat 内容显示的格式,layer 和view 会自动调整适合的格式,一般不需要去设置
//    layer.contentsFormat = kCAContentsFormatRGBA8Uint;

5.变换

对layer显示就行调整,只是改变了显示效果,没有对他的几何属性做改变。主要有:旋转,缩放和平移.

a.仿射变换CGAffineTransform
b.3D变换CATransform3D

6.Key-Value Coding Extensions

核心动画扩展了NSKeyValueCoding协议,因为它属于CAAnimation和CALayer类。此扩展为一些键添加默认值,展开包装约定,并为CGPoint、CGRect、CGSize和CATransform3D类型添加关键路径支持。官方文档

关于kvc可以自行去了解,这里主要对layer的+ (id)defaultValueForKey:(NSString *)key做一个说明。kvc官方文档

// 返回是否归档
- (BOOL)shouldArchiveValueForKey:(NSString *)key;
设置默认值,重写用
+ (id)defaultValueForKey:(NSString *)key;

#import "CAKeyValueCodingLayer.h"

@implementation CAKeyValueCodingLayer

// NSKeyValueCoding 设置默认返回key对应的值
// 对用设置的值类型

+ (id)defaultValueForKey:(NSString *)key {

    if ([key isEqualToString:@"masksToBounds"]) {
        return [NSNumber numberWithBool:YES];
    }
    return [super defaultValueForKey:key];
}

@end
CAKeyValueCodingLayer *layer = [CAKeyValueCodingLayer layer];
NSLog(@"masksToBounds = %@", [layer valueForKey:@"masksToBounds"]);
NSLog(@"someKey1 = %@", [layer valueForKey:@"someKey"]);
// 支持 NSKeyValueCoding Key-Value Coding Extensions
[layer setValue:[NSNumber numberWithInteger:50] forKey:@"someKey"];
NSNumber *someKeyValue = [layer valueForKey:@"someKey"];
NSLog(@"someKey2 = %@", someKeyValue);
输出图

masksToBounds默认的值应该为no,上面的代码把默认值设为了yes。

7.改变view默认带有layer的类型
view默认带有layer,可以通过重写+ (Class)layerClass改变layer的类型
大多数view默认会备份这个view。一般情况下使用默认就可以。
但是可能需要更改的情况,列举几种情况(创建后,视图的图层对象不能更改。):
a.使用 Metal 和 OpenGL ES,来进行绘制,使用 CAMetalLayer or CAEAGLLayer
b.使用一个专门的layer类型,来提高性能
c.利用一些专门的核心动画层类,例如粒子发射器或复制器,CAEmitterLayer,CAReplicatorLayer

#import "CAMetalLayerView.h"

@implementation CAMetalLayerView

+ (Class)layerClass {

    return [CAMetalLayer class];
}

@end
CAMetalLayerView *view = [[CAMetalLayerView alloc] init];
NSLog(@"%@", NSStringFromClass(view.layer.class));
打印结果

这里把UIView的layer类型改成了 CAMetalLayer。

8.手动给sublayers布局

手动布局主要有2种方式:
a.实现CALayerDelegate代理- (void)layoutSublayersOfLayer:(CALayer *)layer
b.自定义layer实现覆盖父类- (void)layoutSublayers;

#import <QuartzCore/QuartzCore.h>

@interface CACustomDisplayLayer : CALayer

@property (nonatomic, strong) CALayer *layer1;
@property (nonatomic, strong) CALayer *layer2;
@property (nonatomic, strong) CALayer *layer3;

@end

#import "CACustomDisplayLayer.h"
#import <UIKit/UIKit.h>

@implementation CACustomDisplayLayer

// 自定义Layer, 覆盖这个实现,可以实现手动给sublayer布局。
- (void)layoutSublayers {

    [super layoutSublayers];
    CGFloat width = floor((CGRectGetWidth(self.bounds) - 24) * 0.5);
    CGFloat height = floor((CGRectGetHeight(self.bounds) - 24) * 0.5);
    
    self.layer1.frame = CGRectMake(8, 8, width, height);
    self.layer2.frame = CGRectMake(16 + width, 8, width, height);
    self.layer3.frame = CGRectMake(8, 16 + height, width, height);
    
}

- (CALayer *)layer1 {

    if (!_layer1) {
        _layer1 = [CALayer layer];
        _layer1.backgroundColor = [UIColor yellowColor].CGColor;
        [self addSublayer:_layer1];
    }
    return _layer1;
}

- (CALayer *)layer2 {
    
    if (!_layer2) {
        _layer2 = [CALayer layer];
        _layer2.backgroundColor = [UIColor redColor].CGColor;
        [self addSublayer:_layer2];
    }
    return _layer2;
}

- (CALayer *)layer3 {
    
    if (!_layer3) {
        _layer3 = [CALayer layer];
        _layer3.backgroundColor = [UIColor blueColor].CGColor;
        [self addSublayer:_layer3];
    }
    return _layer3;
}

@end
// CACustomDisplayLayer 里面只有3一样,但是颜色不一样的图形
CACustomDisplayLayer *displayLayer = [CACustomDisplayLayer layer];
displayLayer.bounds = CGRectMake(0, 0, 200, 200);
displayLayer.position = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
displayLayer.backgroundColor = [UIColor orangeColor].CGColor;
displayLayer.opacity = 0.6;

displayLayer.delegate = self;
[self.view.layer addSublayer:displayLayer];

// 再最后再添加一个颜色不一样,其他一样的图形
CALayer *sub_displayLayer = [CALayer layer];
sub_displayLayer.backgroundColor = [UIColor grayColor].CGColor;
[displayLayer addSublayer:sub_displayLayer];

// 4秒之后改变displayLayer.bounds,观察 displayLayer,sublayers是否自定调节
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//
//        displayLayer.bounds = CGRectMake(0, 0, 150, 150);
//    });
效果图.png

这样做有一个好处,就是当你改变了layer的大小,layer会自动调用相应的方法,对子layer做调整。

三.CALayer其他使用
待补充

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容