「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 绘制

相关推荐
SoraLuna1 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
阿7_QuQ2 小时前
怎么在Windows上远程控制Mac电脑?
macos
小路恢弘7 小时前
使用Mac自带共享实现远程操作
macos
阿髙15 小时前
macos 隐藏、加密磁盘、文件
macos
minos.cpp18 小时前
Mac上Stable Diffusion的环境搭建(还算比较简单)
macos·ai作画·stable diffusion·aigc
追光天使1 天前
Mac/Linux 快速部署TiDB
linux·macos·tidb
wzkttt1 天前
Mac gfortran编译fortran出错
macos·gfortran
刘小哈哈哈1 天前
iOS 多个输入框弹出键盘处理
macos·ios·cocoa
草明2 天前
macOS 显示或不显示隐藏文件
macos
刘贤松2 天前
构建MacOS应用小白教程(打包 & 签名 & 公证 & 上架)
macos