「OC」探索CALayer:基础知识与实用技巧简要介绍

「OC」探索CALayer:基础知识与实用技巧简要介绍

文章目录

前言

在顺利完成暑假之中的任务之后,终于可以学习一点之前一直没有时间学习的内容啦。上一片文章写到「iOS」自定义Modal转场------抽屉视图的实现,我们已经成功的实现了抽屉视图,接下来是要实现cell之中的圆角。我看到要想实现这个圆角cell,需要OC之中的图形绘制CALayer的内容,于是我继续对CALayer进行学习,如果篇幅过长那么会将圆角cell的实现放到后面来。

认识CALayer

CALayer是属于Core Animation,简单说就是呈现内容和动画的层。

在iOS之中我们能够看到的基本上都是UIView,比如一个按钮,一个文本框等等...

那么这些UIView能够被显示在屏幕之中被我们看到,其实是就是由于这个UIView的layer属性(也就是CALayer对象),当这个UIView需要倍显示在屏幕之上的时候,就会通过调用drawRect:这个方法,访问图层进行绘制,可以说UIView本身是没有显示的功能,只有它内部的层级才具有显示功能。

我们可以通过以下方法获取UIView之中的图层

objc 复制代码
CALayer *myLayer = myView.layer;

CALayer的相关属性

可以看到CALayer有着丰富的属性可以用来:

  • 调整图层的大小和位置
  • 调整图层的背景颜色
  • 修改图层的内容 (一个图片,或者是用 CoreGraphics 绘制的东西)
  • 图层是否圆角
  • 添加黑色投影
  • 添加描边的边框

UIView和CALayer

区别

CAlayerUIView最大的不同是CALayer不处理用户的交互。CALayer并不清楚具体的响应链(iOS通过视图层级关系用来传送触摸事件的机制),于是它并不能够响应事件,即使它提供了一些方法来判断是否一个触点在图层的范围之内。

UIApplicationUIViewControllerUIView、和所有从UIView派生出来的UIKit类(包括UIWindow)都直接或间接地继承自UIResponder类。在 UIResponder中定义了处理各种事件和事件传递的接口, 而 CALayer直接继承 NSObject,并没有相应的处理事件的接口。

如果显示出来的东西需要跟用户进行交互的话,那我们肯定要用UIView;如果不需要跟用户进行交互,我们尽量使用`CALayer,因为它少了事件处理的功能,性能会高一些

联系

代理关系:UIView 实现了 CALayerDelegate 协议,这意味着当系统需要绘制 CALayer 的内容时,实际上是 UIView 在幕后调用相关方法进行绘制。这种设计使得开发者可以在 UIView 的上下文中定制内容绘制逻辑,同时利用 CALayer 的高效渲染能力。

属性映射:许多 UIView 的视觉属性(如背景色、边框等)实际上是对内部 CALayer 相关属性的封装。更改 UIView 的这些属性时,实际上是在更改其 CALayer 的对应属性。

创建UIView和CALayer的原因

我们都知道,UIView和CALayer为两个平行的层级关系,那么为什么要这么设计呢?

这里我直接引用大佬博客之中的内容:

原因在于要做职责分离,这样也能避免很多重复代码。在iOS和Mac OS两个平台上,事件和用户交互有很多地方的不同,基于多点触控的用户界面和基于鼠标键盘有着本质的区别,这就是为什么iOS有UIKit和UIView,但是Mac OS有AppKit和NSView的原因。他们功能上很相似,但是在实现上有着显著的区别。

绘图,布局和动画,相比之下就是类似Mac笔记本和桌面系列一样应用于iPhone和iPad触屏的概念。把这种功能的逻辑分开并应用到独立的Core Animation框架,苹果就能够在iOS和Mac OS之间共享代码,使得对苹果自己的OS开发团队和第三方开发者去开发两个平台的应用更加便捷。

开始创建CALayer

我们先创建一个CALayer并且把它加入到视图控制器之中,然后我们再进行相关内容的讲解

objc 复制代码
- (void)viewDidLoad {
    [super viewDidLoad];
    self.layer = [[CALayer alloc] init];
  //背景设置
    self.layer.backgroundColor = [UIColor redColor].CGColor;
  //布局
    self.layer.bounds = CGRectMake(0, 0, 100, 100);
    self.layer.position = CGPointMake(100, 100);
    self.layer.anchorPoint = CGPointMake(0, 0);
  //旋转
    self.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
  //设置border属性
    self.layer.borderWidth = 4;
    self.layer.borderColor = [UIColor purpleColor].CGColor;
  //裁切
    self.layer.cornerRadius = 10.0;
    self.layer.masksToBounds = YES;
  // 将layer添加到图层
    [self.view.layer addSublayer:self.layer];
}

视图层级

我们从视图层级的角度来看,其实不难发现他没有被单独作为一个模块列出

CALayers 和 Sublayers

就像 UIView 有很多 subview 一样, CALayer 也有它的 sublayer

视图树中每一个 view 的根 layer 根据视图关系, 形成相同关系的 layer 树, 同时每一个根 layer 还可以有自己的 sublayer, 如下图所示:

position与anchorPoint(锚点)

  • position属性决定该控件在父控件中的位置,以父控件的左上角为原点
  • anchorPoint属性决定该控件上的哪个点位于position位置

若position为(100, 100),anchorPoint为(0, 0 ),如图

若position为(100, 100),anchorPoint为(0.5, 0.5),如图

当然CALayer还是可以使用frame进行布局,只是如果使用CALayer的属性进行布局在灵活性方面则更占优

CGImage和CGColor

CGImage 是 Core Graphics 框架中用于表示图像的类。它提供了对图像数据的低级别控制,适用于图像处理、渲染和绘制。

用途 : CGImage 主要用于表示和操作位图图像数据。它是 UIImage 的底层表示形式,能够提供直接的图像数据访问和操作。

CGColor 是 Core Graphics 框架中用于表示颜色的类。它用于描述颜色的具体值,包括色彩空间、颜色分量等。

用途 : CGColor 用于在 Core Graphics 和 Core Animation 中指定颜色。它是 UIColor 的底层表示形式,用于图层的背景色、边框色等。

我们不用先前我们所熟知的UIColor和UIImage是因为,他们被定义在UIKit.h的框架之中的,而CALayer是QuartzCore框架的内容。

但是,很多情况下,可以通过UIKit对象的特定方法,得到CoreGraphics对象,比如UIImage的CGImage方法可以返回一个CGImageRef

设置旋转

利用transform属性可以设置旋转、缩放等效果

objc 复制代码
CATransform3D CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z);

参数说明

  • angle : 旋转的角度,以弧度为单位。可以使用 M_PIM_PI / 180 将角度转换为弧度。(M_PI 即为 360°)
  • x, y, z: 旋转轴的坐标分量。决定了围绕哪个轴旋转:
    • 如果 x 为 1,y 为 0,z 为 0,则围绕 X 轴旋转。
    • 如果 x 为 0,y 为 1,z 为 0,则围绕 Y 轴旋转。
    • 如果 x 为 0,y 为 0,z 为 1,则围绕 Z 轴旋转。

裁切

另外当 layer 中的绘制内容超过其 frame 的边界时, 可以通过 masksToBounds 属性来决定是否绘制边界以外的内容. 而 view 的 clipsToBounds 属性实际就是在设置 layer 的 maskToBounds 属性. 默认情况下设置为 false, 即要绘制超过边界的内容.

objc 复制代码
    self.layer.cornerRadius = 10.0;
    self.layer.masksToBounds = YES;

border属性

border即为

objc 复制代码
//设置border属性
imageLayer.borderWidth = 2;
imageLayer.borderColor = [UIColor purpleColor].CGColor;

隐式动画

为了探究CALayer自带的隐式动画,我自己写了一个好玩的内容

复制代码
#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, strong) CALayer *layer;
@property (nonatomic, strong) UIView *views;
@property (nonatomic, strong) NSTimer *timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    
    self.layer = [[CALayer alloc] init];
    self.layer.backgroundColor = [UIColor redColor].CGColor;
    self.layer.bounds = CGRectMake(0, 0, 100, 100);
    self.layer.position = CGPointMake(100, 100);
    self.layer.anchorPoint = CGPointMake(0, 0);
    self.layer.cornerRadius = 10.0;
    self.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
    [self.view.layer addSublayer:self.layer];

    self.views = [[UIView alloc] init];
    self.views.backgroundColor = [UIColor redColor];
    self.views.frame = CGRectMake(50, 250, 100, 100);
    [self.view addSubview:self.views];
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                                  target:self
                                                selector:@selector(changeColor)
                                                userInfo:nil
                                                 repeats:YES];
}

- (void)changeColor {
    // 生成随机颜色
    CGFloat red = arc4random_uniform(256) / 255.0;
    CGFloat green = arc4random_uniform(256) / 255.0;
    CGFloat blue = arc4random_uniform(256) / 255.0;
    
    self.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
    self.views.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
}
@end

通过动图我们可以看到,我们在没有添加任何动画的情况下CALayer,在颜色发生变化时,会自动产生动画,以下是代码运行时的变化过程。

每一个UIView内部都默认关联着一个CALayer,我们可用称这个Layer为Root Layer(根层)。所有的非Root Layer,也就是手动创建的CALayer对象,都存在着隐式动画。我们对UIView的属性修改时时不会产生默认动画,而对单独 layer属性直接修改会,这个默认动画的时间缺省值是0.25s.

当对非Root Layer的部分属性进行相应的修改时,默认会自动产生一些动画效果:

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

自定义CALayer

重写CALayer的子类

我们可以通过创建CALayer的子类来描绘我们想要实现的layer,示例如下:

复制代码
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN

@interface ImageLayer : CALayer

@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) UIColor *background;

@end

NS_ASSUME_NONNULL_END
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
#import "ImageLayer.h"

@implementation ImageLayer

- (void)drawInContext:(CGContextRef)ctx {
    // Set the background color
    CGContextSetFillColorWithColor(ctx, self.background.CGColor);
    CGContextFillRect(ctx, self.bounds);
    
    if (self.image) {
        // Draw the image centered in the layer
        CGRect imageRect;
        imageRect.size = self.image.size;
        imageRect.origin.x = (CGRectGetWidth(self.bounds) - imageRect.size.width) / 2;
        imageRect.origin.y = (CGRectGetHeight(self.bounds) - imageRect.size.height) / 2;
        
        CGContextDrawImage(ctx, imageRect, self.image.CGImage);
    }
}


@end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- (void)viewDidLoad {
    [super viewDidLoad];
    
    ImageLayer *imagelayer = [ImageLayer layer];
    imagelayer.image = [UIImage imageNamed:@"back3.jpeg"];
    imagelayer.background = [UIColor yellowColor];
    imagelayer.frame = CGRectMake(50, 50, 200, 400);
    [imagelayer setNeedsDisplay];
    [self.view.layer addSublayer:imagelayer];
}

通过运行代码,我们可以获得以下CALayer

实现CALayer协议方法

objc 复制代码
//创建图层
CALayer *imageLayer = [[CALayer alloc] init];
//设置代理
imageLayer.delegate = self;
imageLayer.bounds = CGRectMake(0, 0, 100, 100);
imageLayer.position = CGPointMake(100, 200);
[imageLayer setNeedsDisplay];
[self.view.layer addSublayer:imageLayer];

实现代理方法

复制代码
- (void)drawLayer:(nonnull CALayer *)layer inContext:(nonnull CGContextRef)ctx
{
	//通过绘图方法绘制内容
}

参考文章

iOS动画篇_CALayer这些牛逼的子类你造吗

读 iOS核心动画高级技巧

CALayer_超经典的阐述原理

详解CALayer 和 UIView的区别和联系

读书笔记: iOS Layer 绘制

相关推荐
YoungHong19928 小时前
Claude Code & 智谱GLM-5.1 环境配置指南 (Windows/macOS/Ubuntu)
windows·ubuntu·macos
全栈攻略9 小时前
老版本Docker Desktop for Mac 历史版本下载大全(macOS 10.15/11/12)
macos·docker·容器
YoungHong19929 小时前
【图文】Codex接入Kimi K2/GLM-5.1 环境配置指南 (Windows/macOS/Ubuntu)
windows·ubuntu·macos
leluckys9 小时前
Jenkins CI/CD 持续集成专题九 -Mac服务器上配置Jenkins实现iOS项目自动打包
macos·ci/cd·jenkins
和沐阳学逆向18 小时前
iOS逆向_古法逆向_Instagram最新版抓包
macos·ios·cocoa
自学AI的鲨鱼儿1 天前
mac npm 安装 codex 报错 npm ENOTEMPTY
macos·npm·codex
Sephiroth.Ma1 天前
Mac 提示“Docker 已损坏,无法打开”?我这样排查后 10 分钟修好
macos·docker·容器
量子炒饭大师1 天前
【OpenClaw修炼宝典】—— 【macOS安装篇】想玩《爪子船长》复刻版却卡在安装?OpenClaw 从零环境搭建与编译全攻略 (小白避坑指南)
macos·openclaw·小龙虾·龙虾
JFSJHFZJ1 天前
解密iPhone核心技术,读懂苹果的硬实力
ios·cocoa·iphone