【iOS】——浅析CALayer

news2024/11/24 4:33:07

文章目录

  • 一、CALayer介绍
  • 二、UIview与CALayer
    • 1.区别
    • 2.联系
  • 三、CALayer的使用
    • 1.初始化方法
    • 2.常用属性
  • 四.CALayer坐标系
    • 1.position属性和anchorPoint属性
    • 2.position和anchorPoint的关系
    • 3.position、anchorPoint和frame的关系
  • 五、CALayerDelegate
  • 六、CALayer绘图机制
    • 1.绘图流程
    • 2.绘图方法
      • 1.图层代理绘制
      • 2.自定义图层drawInContext:方法
  • 七、CALayer处理点击事件
    • 1.方法一:convertPoint:
    • 2.方法二:hitTest:
  • 八、隐式动画


一、CALayer介绍

在官方文档中CALayer是管理基于图像的内容并允许您对该内容执行动画的对象。通俗来说就是在iOS中我们能看到的所有的UIView对象例如文本框、按钮、输入框等等之所以能够显示到屏幕上就是因为该UIView对象内部有一个图层专门用来显示,也就是CALayer对象,通过访问layer属性便可以访问它的图层。

@property(nonatomic,readonly,strong)CALayer  *layer;    

每当我们创建一个UIView对象时并将其添加到视图层级后,UIKit 会为其自动创建并关联一个 CALayer(RootLayer)。当视图需要显示时,系统会触发视图的 layoutSubviews 方法(如有必要)进行布局调整,然后调用drawRect:方法进行绘图。绘制完成后,CALayer 的内容被更新,并通过渲染管线最终显示到屏幕上。也就是说UIView本身不具备显示功能,而是它内部的图层有显示功能。

二、UIview与CALayer

1.区别

  • UIView:继承自 UIResponder, 主要负责事件响应,属于基于 UIKit 框架
  • CALayer:继承自 NSObject, 负责图像渲染,属于 QuartzCore 框架

在这里插入图片描述

将图像渲染和事件响应这两个功能分别去实现而不让 UIView 具有直接具有图像渲染是因为

1.CALayer 所属的 QuartzCore 框架是可以跨平台使用的,在 iOS和MacOS 中都可以使用,但是UIView只能在iOS中使用,在MacOS中使用Application Kit,在这两个系统里,页面绘图框架是可以公用的,但是两个系统的交互方式却不相同,一个是通过触摸事件,另一个是通过鼠标和键盘。
2.UIView 的主要职责是负责接收并响应事件,而 CALayer 的主要职责是负责显示 UI。这里就遵循了软件工程中的“单一职责原则”,使得每个组件专注于自己的核心任务,提高了代码的可读性、可维护性和可扩展性。

由于CALayer只涉及控件在屏幕上的显示而没有事件响应,因此当只需要显示控件而不需要响应事件的时候可以优先选择CALayer,避免不必要的开销。

在这里插入图片描述

UIView 中有两个属性与图层相关分别是layer属性和layerClass属性:

  • layer 属性返回的是 UIView 所持有的主 Layer(RootLayer) 实例,我们可以通过其来设置 UIView中 layer 的一些属性例如阴影、圆角、边框、背景颜色等;
  • layerClass属性 则返回 RootLayer 所使用的类,我们可以通过重写该属性,来让 UIView 使用不同的 CALayer例如CAShapeLayer、CATextLayer 等等。

重写 layerClass 属性通常在 UIView 的子类中进行,如下所示:

@interface MyCustomView : UIView

@end

@implementation MyCustomView

+ (Class)layerClass {
    return [MyCustomCALayerSubclass class];
}
@end

2.联系

UIView和CALayer是相互依赖的关系。UIView依赖于CALayer提供的内容,CALayer依赖UIView提供的容器来显示绘制的内容(对应的是backing store, 实际上一个bitmap类型的位图)
CALayer 本身构成了一个层次化的树形结构,这一结构与 UIView 的视图层级密切相关,它们分别可以有自己的SubLayer和SubView,并且可以向它的 RootLayer 上添加子 layer。

Layer 内部有三份layer tree,分别是:

  • layer tree(model tree):一般我们称模型树, 也就是各个树的节点的 model 信息, 比如常见的 frame,affineTransform, backgroundColor 等等, 这些 model 数据都是在开发中可以设置的, 我们任何对于view/layer 的修改都能反应在 model tree 中;
  • presentation tree:这是一个中间层,我们 APP 无法主动操作, 这个层内容是 iOS 系统在 Render Server中生成的;
  • render tree:这是直接对应于提交到 render server 上进行显示的树。

Model Tree代表CALayer的真实属性,Presentation Tree对应动画过程中的属性。无论动画进行中还是已经结束,Model Tree都不会发生变化,变化的是Presentation Tree。而动画结束后,Presentation Tree就被重置回到了初始状态。为了让其保持旋转状态,需要在加两句代码:

layer.fillMode=kCAFillModeForwards;
layer.removedOnCompletion=NO;

请添加图片描述
CALayer 是所有 layer 的基类,其派生类会有一些特定的功能,比如绘制文本的 CATextLayer、渐变效果的 CAGradientLayer 等等。种类如下图所示

请添加图片描述
我们通常见到的 layer 都是依附于一个 UIView,但是也有一些单独的 layer 不需要附加到 UIView 上,就可以直接在屏幕上显示内容,如 AVCaptureVideoPreviewLayer、CAShapeLayer 等。

三、CALayer的使用

前面提到CALayer用来进行内容的绘制和渲染,下面介绍下CALayer应该如何使用

1.初始化方法

//默认初始化方法
- (instancetype)init;
//类方法
+ (instancetype)layer;
//基于 coder 的初始化方法,从 storyboard、xib 文件或归档数据中解码恢复时,系统会使用此方法初始化 CALayer
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder;

2.常用属性


//宽度和高度
@property CGRect bounds;

//位置(默认指中点距离父图层原点的位置,具体由anchorPoint决定)
@property CGPoint position;

//锚点(x,y的范围都是0~1),用于定义图层在自身坐标系统中的旋转、缩放和倾斜等变换操作的参照点,默认值为 (0.5, 0.5),即图层的中心点。
@property CGPoint anchorPoint;

//背景颜色(CGColorRef类型)
@property CGColorRef backgroundColor;

//形变属性
@property CATransform3D transform;

//边框颜色(CGColorRef类型)
@property  CGColorRef  borderColor;

//边框宽度
@property CGFloat borderWidth;

//圆角半径
@property CGFloat cornerRadius;

//内容(比如设置为图片CGImageRef)
@property(retain) id contents;

//不透明度
@property float opacity;

//是否裁剪,如果为YES将会剪掉超出layer边框的部分(包括阴影)
@property BOOL masksToBounds;

如果要将图片作为layer的contents属性代码格式如下:

self.view.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"123"].CGImage); // 跨框架赋值需要进行桥接

这里需要用到桥接,因为imageNamed:方法返回的是OC对象,遵循ARC规则
,而layer.contents接收的是Core Foundation对象,遵循MRC规则,这里用到__bridge 关键字就是告诉编译器进行“无内存管理语义”的类型转换。

注意:
contents 属性的类型为 id。在这种情况下,可以给 contents 属性赋予任何值,项目仍可以编译通过。但是在实践中,如果 content 的值不是 CGImage ,得到的图层将是空白的。
既然如此,为什么要将 contents 的属性类型定义为 id 而非 CGImage。这是因为在 Mac OS 系统中,该属性对 CGImage 和 NSImage 类型的值都起作用,而在 iOS 系统中,该属性只对 CGImage 起作用。

下面是一个关于layer一些常用属性的展示代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //创建CALayer对象
    self.myLayer = [[CALayer alloc] init];
    //设置CALayer对象的位置和大小
    self.myLayer.frame = CGRectMake(100, 300, 100, 100);
    //self.myLayer.position = CGPointMake(100, 300);
    //self.myLayer.bounds = CGRectMake(100, 100, 100, 100);
    //self.myLayer.anchorPoint = CGPointMake(0.5, 0.5);

    //设置背景颜色
    self.myLayer.backgroundColor = [UIColor whiteColor].CGColor;
    //设置阴影颜色
    self.myLayer.shadowColor = [UIColor grayColor].CGColor;
    //设置阴影偏移量
    self.myLayer.shadowOffset = CGSizeMake(10, 10);
    //设置阴影不透明度
    self.myLayer.shadowOpacity = 0.6;
    //设置边框宽度
    self.myLayer.borderWidth = 3.0;
    //设置边框颜色
    self.myLayer.borderColor = [UIColor blackColor].CGColor;
    //设置圆角半径
    self.myLayer.cornerRadius = 12;
    //设置是否裁剪
    self.myLayer.masksToBounds = NO;
    //设置内容
    self.myLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"Apple.jpg.png"].CGImage); // 跨框架赋值需要进行桥接

    //将对象添加到控制器的Layer
    [self.view.layer addSublayer: self.myLayer];
}

运行结果如下:

请添加图片描述

需要注意的是如果将 self.myLayer.masksToBounds设置为YES,那么就会将超过layer边框的部分裁剪掉,也就是说阴影部分就会消失。

如果将self.myLayer.frame = CGRectMake(100, 300, 100, 100);替换成 self.myLayer.position = CGPointMake(100, 300); self.myLayer.bounds = CGRectMake(100, 100, 100, 100);那么测试结果会和前面一样吗

请添加图片描述

可以看到layer的位置发生了改变,这是因为frame属性改变的是layer在父视图中的位置和大小,frame表示图层左上角位于父图层原点(通常是左上角)水平方向 100 点、垂直方向 300 点的位置,而bounds属性只改变layer的大小不改变其位置,position属性改变的是layer中心点在父视图中的位置,一个是layer左上角作为参考点,一个是layer中心点作为参考点因此位置不一样。

四.CALayer坐标系

1.position属性和anchorPoint属性

  • position属性 表示了layer的anchorPoint属性设置的锚点距离父图层左上角(0,0)的位置。其计算公式如下:

position.x = frame.origin.x + anchorPoint.x * bounds.size.width ;
position.y = frame.origin.y + anchorPoint.y * bounds.size.height 。

  • anchorPoint属性设置的锚点是用于定义图层在自身坐标系统中的旋转、缩放和倾斜等变换操作的参照点,它是一个 CGPoint类型的值,其坐标范围是 [0, 1],表示图层内部坐标系统的相对位置。默认值为 (0.5, 0.5),即图层的中心点。它是相对于图层自身的坐标系统而言的,而非其父图层。这意味着 anchorPoint 的位置是基于图层的 bounds(即图层内部内容的大小)来确定的。

在这里插入图片描述

在这里插入图片描述

2.position和anchorPoint的关系

  1. 前面提到position表示了layer的anchorPoint属性设置的锚点距离父图层左上角(0,0)的位置,如果修改了anchorPoint属性的话,position是否会发生改变呢,答案是不会。
//设置CALayer对象的位置
    self.myLayer.frame = CGRectMake(100, 300, 100, 100);
    self.myLayer.anchorPoint = CGPointMake(0, 0);
    NSLog(@"x:%f y:%f",self.myLayer.position.x, self.myLayer.position.y);

请添加图片描述

 //设置CALayer对象的位置
    self.myLayer.frame = CGRectMake(100, 300, 100, 100);
    self.myLayer.anchorPoint = CGPointMake(1, 1);
    NSLog(@"self.myLayer.position.x:%f \n self.myLayer.positiony:%f",self.myLayer.position.x, self.myLayer.position.y);

在这里插入图片描述
可以看到虽然改变了anchorPoint但是position没有发生变化。

  1. 如果改变position的话,会导致 anchorPoint 的变化吗?答案也是不会。
self.myLayer.frame = CGRectMake(100, 300, 100, 100);
    self.myLayer.position = CGPointMake(100, 100);
    NSLog(@"self.myLayer.anchorPoint.x:%f \n self.myLayer.anchorPoint.y:%f",self.myLayer.anchorPoint.x, self.myLayer.anchorPoint.y);

请添加图片描述

self.myLayer.frame = CGRectMake(100, 300, 100, 100);
    self.myLayer.position = CGPointMake(100, 100);
    NSLog(@"self.myLayer.anchorPoint.x:%f \n self.myLayer.anchorPoint.y:%f",self.myLayer.anchorPoint.x, self.myLayer.anchorPoint.y);

请添加图片描述
可以看到修改了position但是anchorPoint没有改变,这是因为它是相对于图层自身的坐标系统而言的,取决于自身图层的大小。

3.position、anchorPoint和frame的关系

CALayerframe 在文档中被描述为是一个计算型属性,它是从 boundsanchorPointposition 的值中派生出来的。为此属性指定新值时,图层会更改其 position 和 bounds 属性以匹配您指定的矩形

那它们是如何决定 frame 的?根据图片可以套用如下公式:

frame.x = position.x - anchorPoint.x * bounds.size.width ;
frame.y = position.y - anchorPoint.y * bounds.size.height 。

这就解释了为什么修改 position 和 anchorPoint 会导致 frame 发生变化

因此,更改 anchorPoint 会直接影响图层在屏幕上显示的位置,除非同时调整 position 以保持视觉位置不变。假设有一个图层,其 position 为 (100, 100),anchorPoint 为默认的 (0.5, 0.5),且 bounds 为 (100, 100)。此时,图层的中心点位于父图层坐标系中的 (100, 100)。如果将 anchorPoint 更改为 (0, 0)(左上角),为了保持图层在屏幕上的视觉位置不变,需要将 position 调整为 (50, 50),这样图层的左上角仍保持在 (100, 100) 处。

注意:
如果修改了 frame 的值是会导致 position 发生变化的,因为 position 是基于父图层定义的;frame 的改变意味着它自身的位置在父图层中有所改变,position 也会因此改变。
但是修改了 frame 并不会导致 anchorPoint 发生变化,因为 anchorPoint 是基于自身图层定义的,无论外部怎么变,anchorPoint 都不会跟着变化。

五、CALayerDelegate

可以使用 delegate (CALayerDelegate) 对象来提供图层的内容,处理任何子图层的布局,并提供自定义操作以响应与图层相关的更改。如果图层是由 UIView 创建的,则该 UIView 对象通常会自动指定为图层的委托。跳转到CALayerDelegate定义发现其代理方法有以下这五个方法,且都是可选方法而非必选。所以delegate 只是另一种为图层提供处理内容的方式,并不是唯一的。UIView 的显示跟它图层委托没有太大关系。
在这里插入图片描述

  • - (void)displayLayer:(CALayer *)layer;
    当图层标记其内容为需要更新 (调用 setNeedsDisplay() 方法) 时,调用此方法。如果定义了此方法,应当在这里实现整个显示过程,通常通过设置contents属性来完成。这允许自定义图层内容的渲染逻辑。

下面是一段示例代码:

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //创建CALayer对象
    self.myLayer = [[CALayer alloc] init];
    //设置CALayer对象的位置
    self.myLayer.position = CGPointMake(200, 200);
    self.myLayer.bounds = CGRectMake(100, 100, 100, 100);
    //设置背景颜色
    self.myLayer.backgroundColor = [UIColor whiteColor].CGColor;
    //设置阴影颜色
    self.myLayer.shadowColor = [UIColor grayColor].CGColor;
    //设置阴影偏移量
    self.myLayer.shadowOffset = CGSizeMake(10, 10);
    //设置阴影不透明度
    self.myLayer.shadowOpacity = 0.6;
    //设置边框宽度
    self.myLayer.borderWidth = 3.0;
    //设置边框颜色
    self.myLayer.borderColor = [UIColor blackColor].CGColor;
    //设置圆角半径
    self.myLayer.cornerRadius = 12;
    //设置是否裁剪
    self.myLayer.masksToBounds = NO;
    //设置内容
    self.myLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"Apple.jpg.png"].CGImage); // 跨框架赋值需要进行桥接
    //设置代理
    self.myLayer.delegate = self;
    //将对象添加到控制器的Layer
    [self.view.layer addSublayer: self.myLayer];
    
   
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.myLayer.anchorPoint = CGPointMake(0.5, 0.5);
    self.myLayer.bounds = CGRectMake(100, 100, 200, 200);
    //更新图层
    [self.myLayer setNeedsDisplay];
    
}

- (void)displayLayer:(CALayer *)layer {
    self.myLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"AppleLogo.png"].CGImage);
}
@end

运行结果如下:
请添加图片描述

点击后更新图层

请添加图片描述

  • - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
    - (void)displayLayer:(CALayer *)layer 一样,但是可以使用图层的 CGContext也就是上下文 来实现显示的过程
  • - (void)layerWillDraw:(CALayer *)layer;

在默认的 -display 方法执行前调用,允许代理在调用 -drawLayer:inContext: 之前配置任何影响图层内容的图层状态,比如contentsFormat(内容格式)和opaque(是否不透明)。如果代理实现了 -displayLayer: 方法,则此方法不会被调用。

  • - (void)layoutSublayersOfLayer:(CALayer *)layer;
    在默认的 -layoutSublayers 实现中调用,且在系统布局之前。当发现边界发生变化并且其 sublayers 可能需要重新排列时(例如通过 frame 改变大小),将调用此方法。注意,如果代理方法被调用,系统布局将被忽略,从而允许代理完全控制子图层的布局过程。

1

  • - (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;

当图层需要对特定事件(如动画)作出响应时,由默认的 -actionForKey: 方法调用。应当返回一个遵循CAAction协议的对象来定义该事件的行为。可以返回nil表示代理没有为此事件指定行为,或者返回[NSNull null]明确表示不进行进一步的搜索,即不调用+defaultActionForKey:方法。

六、CALayer绘图机制

1.绘图流程

下图是 CALayer 在渲染之前的流程:

  1. 首先调用[UIView setNeedsDisplay][view.layer setNeedsDisplay]:来给一个视图或者其对应的图层打上脏标记表示需要重新绘制,但此时它还显示原来的内容,等到下一轮 RunLoop 修改才会生效。
  2. 接着当图层需要重新绘制时,会调用display方法,这个方法负责更新图层的内容,然后检查图层的代理(delegate)是否响应displayLayer:方法:
  3. 如果图层有一个代理,并且代理实现了displayLayer:方法 (YES),那么系统会调用这个方法来异步绘制图层的内容,这意味着可以通过实现displayLayer:方法来自定义图层的绘制过程,这个过程是异步进行的,不会阻塞主线程。
  4. 如果代理不响应displayLayer:方法(NO),则进入“系统绘制流程”。在这种情况下,系统会按照默认的方式绘制图层的内容。

请添加图片描述
下面是系统绘制的流程:

  1. 系统绘制时, 会先创建 用于存储像素数据的缓存区域backing storage(CGContextRef),我们可以理解为 CGContextRef 上下文;这个上下文是绘制的基础,所有图形、颜色等绘制指令都会在这个上下文中执行。
  2. 判断 layer 是否有 delegate,然后进入到不同的渲染分支中去,但是最后无论哪两个分支, 都有 CAlayer 上传 backing store。
  3. 如果有 delegate,则会执行 [layer.delegate drawLayer:inContext],然后在这个方法中会调用 view 的 drawRect: 方法,也就是我们重写 view 的 drawRect: 方法才会被调用到;
  4. 如果没有 delegate,会调用 layer 的 drawInContext 方法,也就是我们可以重写的 layer 的该方法,此刻会被调用到;

在这里插入图片描述

drawRect: 方法是在 CPU 执行的, 在它执行完之后, 通过 context 将数据 (通常情况下这里的最终结果会是一个 bitmap, 类型是 CGImageRef) 写入 backing store, 通过 rendserver 交给 GPU 去渲染,将 backing store 中的 bitmap 数据显示在屏幕上。

下面是异步绘制的流程

  1. 把UIView 显示的内容(包括 UILabel 的文字,UIImageView 的图片等)绘制生成的位图(bitmap)在子线程完成。
  2. 然后在回到主线程把bitmap赋值给view.layer.content属性。

在这里插入图片描述

  1. 首先在主线程中调用[AsyncDrawingView setNeedsDisplay]来标记视图需要重新绘制。
  2. 接着当视图需要重绘时,会调用[CALayer display]方法。
  3. display方法会调用[AsyncDrawingView displayLayer:]方法,这个方法会在全局队列中异步执行,也就是进行异步绘制工作。
  4. 在异步绘制工作中,使用CGContextCreate()创建一个位图上下文,并使用Core Graphic API进行绘制操作。
  5. 完成绘制后,通过CGBitmapContextCreateImage()将位图上下文转换为图片。
  6. 最后切换回主线程,调用[CALayer setContents:]方法,将绘制好的图像设置为layer的contents。

2.绘图方法

CAlayer图层绘图有两种方法,不管使用哪种方法绘制完必须调用图层的setNeedDisplay方法,例如[self.view.layer setNeedDisplay];,下面是图层绘制的两种方法:

  • 通过图层代理drawLayer:inContext:方法绘制
  • 通过自定义图层drawInContext:方法

1.图层代理绘制

通过代理方法进行图层绘图只要指定图层的代理,然后在代理对象中重写-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx 方法即可。需要注意这个方法虽然是代理方法但是不用手动实现CALayerDelegate,因为CALayer定义中给NSObject做了分类扩展,所有的NSObject都包含这个方法。另外设置完代理后必须要调用图层的setNeedDisplay方法,否则绘制的内容无法显示。

下面是示例代码:

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //创建CALayer对象
    self.myLayer = [[CALayer alloc] init];
    //设置CALayer对象的位置
    self.myLayer.position = CGPointMake(200, 200);
    self.myLayer.bounds = CGRectMake(100, 100, 100, 100);
    //设置背景颜色
    self.myLayer.backgroundColor = [UIColor whiteColor].CGColor;
    //设置阴影颜色
    self.myLayer.shadowColor = [UIColor grayColor].CGColor;
    //设置阴影偏移量
    self.myLayer.shadowOffset = CGSizeMake(10, 10);
    //设置阴影不透明度
    self.myLayer.shadowOpacity = 0.6;
    //设置边框宽度
    self.myLayer.borderWidth = 3.0;
    //设置边框颜色
    self.myLayer.borderColor = [UIColor blackColor].CGColor;
    //设置圆角半径
    self.myLayer.cornerRadius = 12;
    //设置是否裁剪
    self.myLayer.masksToBounds = NO;
    //设置代理
    self.myLayer.delegate = self;
    //将对象添加到控制器的Layer
    [self.view.layer addSublayer: self.myLayer];
    [self.myLayer setNeedsDisplay];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    CGContextSaveGState(ctx);
    //解决图形上文形变,图片倒立的问题
    
    CGContextScaleCTM(ctx, 1, -1);
    CGContextTranslateCTM(ctx, 0, -100);
    UIImage* image = [UIImage imageNamed:@"Apple.jpg.png"];
    CGContextDrawImage(ctx, CGRectMake(0, 0, 100, 100), image.CGImage);
    CGContextRestoreGState(ctx);
}

实现带阴影效果的圆形图片裁剪
我们知道当maskToBounds设置为YES时将会裁剪掉超过图层边框范围的部分,而阴影正是属于那部分的内容,如果要实现带阴影效果的圆形图片裁剪可以通过使用两个大小一样的图层,下面的图层负责绘制阴影,上面的图层用来显示图片

示例代码如下:

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //创建CALayer对象
    [self shadowLayerCreate];
    [self myLayerCreate];
}
- (void)shadowLayerCreate {
    CALayer* layerShadow = [[CALayer alloc]init];
        layerShadow.bounds = CGRectMake(100, 100, 100, 100);
        layerShadow.position = CGPointMake(200, 200);
        layerShadow.cornerRadius = layerShadow.bounds.size.width / 2;
        layerShadow.shadowColor = [UIColor grayColor].CGColor;
        layerShadow.shadowOffset = CGSizeMake(2, 1);
        layerShadow.borderColor = [UIColor grayColor].CGColor;
        layerShadow.shadowOpacity = 1;
        layerShadow.backgroundColor = [UIColor blackColor].CGColor;
        layerShadow.borderWidth = 3.0;
        [self.view.layer addSublayer:layerShadow];
}
- (void)myLayerCreate {
    self.myLayer = [[CALayer alloc] init];
    //设置CALayer对象的位置
    self.myLayer.position = CGPointMake(200, 200);
    self.myLayer.bounds = CGRectMake(100, 100, 100, 100);
    //设置背景颜色
    self.myLayer.backgroundColor = [UIColor whiteColor].CGColor;
    //设置边框宽度
    self.myLayer.borderWidth = 3.0;
    //设置边框颜色
    self.myLayer.borderColor = [UIColor blackColor].CGColor;
    //设置圆角半径
    self.myLayer.cornerRadius = self.myLayer.bounds.size.width / 2;
    //设置是否裁剪
    self.myLayer.masksToBounds = YES;
    //设置内容
    self.myLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"Apple.jpg.png"].CGImage); // 跨框架赋值需要进行桥接
    //设置代理
    self.myLayer.delegate = self;
    //将对象添加到控制器的Layer
    [self.view.layer addSublayer: self.myLayer];
}

请添加图片描述

需要注意的是先添加阴影layer再添加自己的layer负责阴影layer会覆盖在自己的layer之上。

2.自定义图层drawInContext:方法

在自定义图层中绘图时只要编写一个继承于CALayer的类然后在drawInContext:中绘图即可。要显示图层中绘制的内容也要调用图层的setNeedDisplay方法,否则drawInContext方法将不会调用。

示例代码如下:

#import <QuartzCore/QuartzCore.h>

NS_ASSUME_NONNULL_BEGIN

@interface CustomLayer : CALayer

@end

NS_ASSUME_NONNULL_END

#import "CustomLayer.h"

@implementation CustomLayer

// 初始化方法,如果有自定义属性,需要在这里处理

- (instancetype)init {
    self = [super init];
    if (self) {
        // 初始化自定义设置,如需要
    }
   return self;
}

- (void)drawInContext:(CGContextRef)ctx {
    // 设置绘图颜色、线宽等属性
    CGContextSetRGBFillColor(ctx, 1.0, 0.0, 0.0, 1.0); // 设置填充颜色为红色
    CGContextSetRGBStrokeColor(ctx, 0.0, 0.0, 1.0, 1.0); // 设置描边颜色为蓝色
    CGContextSetLineWidth(ctx, 2.0); // 设置线宽为2.0

    // 自定义绘图命令
    CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)); // 绘制一个椭圆,充满整个图层边界
    CGContextDrawPath(ctx, kCGPathFillStroke); // 填充并描边路径
}

@end

#import <UIKit/UIKit.h>
#import "CustomLayer.h"
@interface ViewController : UIViewController<CALayerDelegate>

@property (strong, nonatomic) CustomLayer *customLayer;

@end
#import "ViewController.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    // 创建自定义图层实例
    self.customLayer = [[CustomLayer alloc] init];
   // 设置图层的frame
   self.customLayer.frame = CGRectMake(50, 50, 200, 200);

   // 添加自定义图层到视图的layer上
   [self.view.layer addSublayer:self.customLayer];
    
}

七、CALayer处理点击事件

前面提到CAlayer继承于继承自 NSObject, 而不是UIResponder类,因此本身不具备响应事件,但是依然有两种方法可以帮助我们实现捕捉并且处理CALayer的点击事件。

1.方法一:convertPoint:

@interface ViewController : UIViewController
@property (nonatomic, strong) CALayer *whiteAppleLayer;
@property (nonatomic, strong) CALayer *blackAppleLayer;

@end

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    self.whiteAppleLayer = [CALayer layer];
    self.whiteAppleLayer.frame = CGRectMake(100, 100, 200, 200);
    self.whiteAppleLayer.backgroundColor = [UIColor whiteColor].CGColor;
    self.whiteAppleLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"AppleLogo.png"].CGImage);
    [self.view.layer addSublayer:self.whiteAppleLayer];
    
    self.blackAppleLayer = [CALayer layer];
    self.blackAppleLayer.frame = CGRectMake(100, 300, 200, 200);
    self.blackAppleLayer.backgroundColor = [UIColor blackColor].CGColor;
    self.blackAppleLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"Apple.jpg.png"].CGImage);
    [self.view.layer addSublayer:self.blackAppleLayer];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CGPoint point = [[touches anyObject] locationInView:self.view];
    CGPoint whitePoint = [self.whiteAppleLayer convertPoint:point fromLayer:self.view.layer];
    CGPoint blackPoint = [self.blackAppleLayer convertPoint:point fromLayer:self.view.layer];
    if ([self.whiteAppleLayer containsPoint:whitePoint]) {
        NSLog(@"whiteApple");
    }
    if ([self.blackAppleLayer containsPoint:blackPoint]) {
        NSLog(@"blackApple");
    } 
}
@end

点击白色苹果layer时会打印whiteApple
请添加图片描述
点击黑色苹果layer后会打印blackApple

请添加图片描述
首先使用locationInView方法获取到点击的点在view(默认为根视图)上的坐标。接着通过convertPoint:fromLayer :方法传入一个CGPoint来转换坐标系,将在其父图层上的坐标转换为相对于图层自身的坐标,这样转换坐标系的方法还有以下几个:

//此方法用于将一个点的坐标从指定的layer坐标系转换到当前调用该方法的图层的坐标系中。
- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer; 
 //与前一个方法相反,此方法将一个点的坐标从当前调用该方法的图层的坐标系转换到指定的layer坐标系中。
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer; 
//转换一个矩形区域的坐标从指定的layer图层坐标系到当前调用该方法的图层坐标系中。
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
//将一个矩形区域的坐标从当前调用该方法的图层坐标系转换到指定的layer图层坐标系中。
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;

得到触摸点相对于图层自身的坐标之后,调用containsPoint:方法。containsPoint:方法传入一个CGPoint类型参数,如果这个点在图层的frame内,则返回YES,否则返回NO。这样,就实现了对CALayer点击事件的处理。

为什么确定触摸点是否落在某个子图层上,就需要转换坐标系呢
因为子图层可能有自己独立的位置、缩放或旋转变换。换句话说,子图层的坐标原点与根视图或父视图的坐标原点可能不一致。为了准确判断触摸点是否在某个子图层内部,就需要将触摸点的坐标从全局坐标系(通常是窗口或根视图的坐标系)转换到该子图层的本地坐标系。这个转换过程考虑了子图层的所有平移、旋转和缩放变换。

2.方法二:hitTest:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CGPoint point = [[touches anyObject] locationInView:self.view];
    CALayer *layer = [self.view.layer hitTest:point];
    if (layer == self.whiteAppleLayer) {
        NSLog(@"whiteApple");
    }else if (layer == self.blackAppleLayer){
        NSLog(@"blackApple");
    }
}

hitTest:同样传入一个CGPoint类型参数,但它的返回值不是BOOL类型,而是图层本身。如果点击的位置在最外层图层之外,则返回nil。

八、隐式动画

每一个UIView内部都默认关联着一个CALayer,我们可称这个Layer为RootLayer(根层),所有的非RootLayer,也就是手动创建的CALayer对象,都存在着隐式动画。
当对非RootLayer的部分属性进行修改时,默认会自动产生一些动画效果而这些属性称为AnimatableProperties(可动画属性)。这个就是隐式动画
下面是几个常见的AnimatableProperties:

        bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画。
        backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画。
        position:用于设置CALayer的位置。修改这个属性会产生平移动画。

如果想关闭隐式动画,可以通过动画事务(CATransaction)关闭。代码如下:

// 开始一个动画事务。
[CATransactionbegin];
//关闭隐式动画
[CATransactionsetDisableActions:YES];
//修改可动画属性
self.myview.layer.position= CGPointMake(10, 10);
//执行动画事务
[CATransactioncommit];

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1649408.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

官方教程来啦!上手体验YashanDB主备部署、同步延迟和自动切换能力

在上一篇深度干货 | 如何兼顾性能与可靠性&#xff1f;一文解析YashanDB主备高可用技术中&#xff0c;我们深入探讨了YashanDB高可用的架构设计原理和关键技术&#xff0c;本文将聚焦于实践操作&#xff0c;快速体验YashanDB的主备高可用能力。 概要 YashanDB提供了不同部署形…

C++程序设计教案

文章目录&#xff1a; 一&#xff1a;软件安装环境 第一种&#xff1a;vc2012 第二种&#xff1a;Dev-C 第三种&#xff1a;小熊猫C 二&#xff1a;语法基础 1.相关 1.1 注释 1.2 换行符 1.3 规范 1.4 关键字 1.5 ASCll码表 1.6 转义字符 2.基本框架 2.1 第一种&…

如果insightface/instantID安装失败怎么办(关于InsightFaceLoader_Zho节点的报错)

可能性有很多&#xff0c;但是今天帮朋友解决问题的时候又收集了一种新的思路。 首先&#xff0c;可以先按照这篇文章里边提到的方法去安装&#xff1a; 【全网最详细】ComfyUI下&#xff0c;Insightface安装指南-聚梦小课堂_insightface如何安装-CSDN博客 其次&#xff0c;…

解决Python中的 `ModuleNotFoundError: No module named ‘fcmeans‘` 错误

ModuleNotFoundError: No module named fcmeans 解决Python中的 ModuleNotFoundError: No module named fcmeans 错误如何解决这个错误fcmeans 库简介应用实例 解决Python中的 ModuleNotFoundError: No module named fcmeans 错误 在进行数据科学或机器学习项目时&#xff0c;…

Linux内核之获取文件系统超级块:sget用法实例(六十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

大眼橙C1 Air投影仪:千元预算内的明智之选

在科技日新月异的今天&#xff0c;投影仪已经不再是会议室或教室的专属&#xff0c;而是越来越多地走入了寻常百姓家。家庭影院的概念越来越流行&#xff0c;尤其在都市人之间逐渐成为一股风尚。市场上投影仪非常多&#xff0c;如何选到一台合适的投影仪也成为困扰广大用户的一…

了解TMS运输管理系统,实现物流高效运转

TMS运输管理系统&#xff08;Transportation Management System&#xff09;是一种集成物流和信息技术的解决方案&#xff0c;通过优化运输流程、实时跟踪货物信息和自动化管理操作&#xff0c;提高物流效率&#xff0c;降低运营成本&#xff0c;实现高效运输。 TMS运输管理系…

软件设计师-重点的构造型设计模式

一、桥接模式&#xff08;Bridge&#xff09;&#xff1a; 意图&#xff1a; 将抽象部分与其实现部分分离&#xff0c;使它们都可以独立地变化。 结构&#xff1a; 适用性&#xff1a; 不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如&#xff0c;这种情况可能是…

探索大模型能力--prompt工程

1 prompt工程是什么 1.1 什么是Prompt&#xff1f; LLM大语言模型终究也只是一个工具&#xff0c;我们不可能每个人都去训一个大模型&#xff0c;但是我们可以思考如何利用好大模型&#xff0c;让他提升我们的工作效率。就像计算器工具一样&#xff0c;要你算10的10倍&#x…

【计算机网络】计算机网络的性能指标

计算机网络的性能指标被用来从不同方面度量计算机网络的性能。常用的八个计算机网络性能指标&#xff1a;速率、带宽、吞吐量、时延、时延带宽积、往返时间、利用率、丢包率。 一.速率 (1) 数据量 比特&#xff08;bit&#xff0c;记为小写b&#xff09;是计算机中数据量的基…

JavaWEB 框架安全:Spring 漏洞序列.(CVE-2022-22965)

什么叫 Spring 框架. Spring 框架是一个用于构建企业级应用程序的开源框架。它提供了一种全面的编程和配置模型&#xff0c;可以简化应用程序的开发过程。Spring 框架的核心特性包括依赖注入&#xff08;Dependency Injection&#xff09;、面向切面编程&#xff08;Aspect-Or…

c++ 线程交叉场景试验

1.需求 1.处理一个列表的数据&#xff0c;要求按照列表的数据处理10个数据 2.可以使用多线程处理&#xff0c;但是针对每个线程&#xff0c;1~10的处理顺序不能变。 3.每个数据的处理必须原子&#xff0c;即只有一个线程可以针对某个数据进行处理&#xff0c;但是10个数据是可…

2024年CSC公派联合培养博士项目申报即将开始~

一、选派计划 联合培养博士研究生面向全国各博士学位授予单位选拔。 联合培养博士研究生的留学期限、资助期限为6-24个月。留学期限应根据拟留学单位学制、外方录取通知&#xff08;或正式邀请信&#xff09;中列明的留学时间确定。个人申报的资助期限应不超过留学期限&#…

79、贪心-跳跃游戏II

思路&#xff1a; 首先理解题意&#xff1a;从首位置跳最少多少次到达末尾。 第一种&#xff1a;使用递归&#xff0c;将所有跳转路径都获取到进行求出最小值。 第二种&#xff1a;使用动态规划&#xff0c;下一次最优取决上一次的最优解 第三针&#xff1a;贪心&#xff…

python数据分析常用基础语法

Python语言基础——语法基础 前言一、变量的介绍与使用变量的介绍变量命名规则变量的使用拓展 二、标识符标识符命名命名规则注意事项 三、数据类型数据类型的介绍数据类型的查看示例 四、输入与输出输入和输出的介绍format格式化输出占位符 五、代码缩进与注释代码缩进 前言 …

Spring Cloud 整合Sentinel

1、引入依赖 版本说明 alibaba/spring-cloud-alibaba Wiki GitHub 父pom <spring.cloud.version>Hoxton.SR12</spring.cloud.version> <spring.cloud.alibaba.version>2.2.10-RC1</spring.cloud.alibaba.version>Sentinel应用直接引用starter <…

0508_IO2

练习&#xff1a; 将一张图片修改为德国国旗 1 #include <stdio.h>2 #include <string.h>3 #include <stdlib.h>4 #include <sys/types.h>5 #include <unistd.h>6 #include <sys/stat.h>7 #include <fcntl.h>8 #include <pthrea…

Codigger:Web应用赋能的分布式操作系统让用户卓越体验

Codigger&#xff0c;作为一个分布式操作系统&#xff0c;其独特之处在于其采用的浏览器/服务器&#xff08;Browser/Server&#xff0c;简称B/S&#xff09;架构。这种架构的核心思想是&#xff0c;通过浏览器来进入工作界面&#xff0c;页面交互部分事务逻辑在前端&#xff0…

1-1ARM开发环境搭建(GD32)

1:安装MDK最好是5.27以及以上版本&#xff0c;避免后续学习中出现相关错误 2&#xff1a;安装芯片支持包 双击安装即可&#xff0c;也可以是默认路径&#xff0c;也可以自己更改路径 3&#xff1a;安装jlink下载器驱动&#xff08;下载调试器&#xff09; 具体安装步骤如下所示…

Mac虚拟机软件哪个好用 mac虚拟机parallels desktop有什么用 Mac装虚拟机的利与弊 mac装虚拟机对电脑有损害吗

随着多系统使用需求的升温&#xff0c;虚拟机的使用也变得越来越普遍。虚拟机可以用于创建各种不同的系统&#xff0c;并按照要求设定所需的系统环境。另外&#xff0c;虚拟机在Mac电脑的跨系统使用以及测试软件系统兼容性等领域应用也越来越广泛。 一、Mac系统和虚拟机的区别 …