iOS 学习之 CALayer 和 UIView 之间的区别与联系

在 iOS 中,所有的 View 都是由一个底层的 layer 来驱动的。View 和它的 layer 之间有着紧密的联系,View 其实直接从 layer 对象中获取了绝大多数它所需要的数据。也有一些单独的 layer,比如 AVCaptureVideoPreviewLayer 和 CAShapeLayer,它们不需要附加到 View 上就可以在屏幕上显示内容。两种情况下其实都是 layer 在起决定作用。当然了,附加到 View 上的 layer 和单独的 layer 在行为上还是稍有不同的。

基本上你改变一个单独的 layer 的任何属性的时候,都会触发一个从旧的值过渡到新值的简单动画(这就是所谓的可动画 animatable)。然而,如果你改变的是 View 中 layer 的同一个属性,它只会从这一帧直接跳变到下一帧。尽管两种情况中都有 layer,但是当 layer 附加在 View 上时,它的默认的隐式动画的 layer 行为就不起作用了。

在平时开发中,我们一般都是使用 UIView 或者其子类来构建视图,但是在设置圆角、阴影等效果的时候,会设置 UIView 中的 Layer 的属性。在如何选择 UIView 和 CALayer 之前,我们应该了解 UIView 跟 CALayer 是什么关系,它们之间是如何协同工作的。

CALayer

概念

CALayer 是数据 QuartzCore 框架里面的 、相对于 UIKit 框架 更于底层、 其主要功能是 负责显示视图和动画、CALayer和UIView 在除了能响应事件上 功能 是一致的、 不过因为其 更加底层 所以 CALayer 有一些接口、 UIView 里面没有。

动画

有时候我们可以直接通过操作 CALayer 去修改视图。但是要注意 隐式动画的发生,CAlayer 有对应的 类方法 可以去把隐式动画关闭。

1
[CATransaction setDisableActions:YES];

我们看到的动画 、实际上在运动的是 CALayer 在动, UIView 并没有参加。
当是如果修改 UIView 主 layer 的话,此时隐式动画会失效,因为:UIView 默认情况下禁止了 layer 动画,但是在 animation block 中又重新启用了它们。

当一个 animatable 属性变化时,layer 会询问代理方法该如何处理这个动画,即需要在代理方法中返回合适的 CAAction 对象。

  • 在 iOS 中,你能看得见摸得着的东西基本上都是 UIView,比如一个按钮、一个文本标签、一个文本输入框、一个图标等等,这些都是 UIView;
  • 其实 UIView 之所以能显示在屏幕上,完全是因为它内部的一个图层;
  • 在创建 UIView 对象时,UIView 内部会自动创建一个图层 (即CALayer对象),通过 UIView 的 layer 属性可以访问这个图层;
  • 当 UIView 需要显示到屏幕上时,会调用 drawRect: 方法进行绘图,并且会将所有内容绘制在自己的图层上,绘图完毕后,系统会将图层拷贝到屏幕上,于是就完成了 UIView 的显示;
  • 换句话说,UIView 本身不具备显示的功能,是它内部的层才有显示功能。

因为 CALayer 是一种更轻量级别的 视图、所以如果不需要响应点击事件的时候,可以直接使用其去显示即可以提升性能。

UIView

UIView 是 iOS 系统中界面元素的基础, 所有的界面元素都继承自它, UIView 本身完全是由CoreAnimation 来实现. 真正的绘图部分, 是由一个 CALayer 类来管理. UIView 更像是一个 CALayer 的管理器, 所以访问它的与绘图和坐标相关的属性, 如 frame, bounds 等, 实际上都是在访问其所包含的 CALayer 的相关属性。因此, 可以在所有 UIView 的子类上实现动画效果。

UIView 继承自 UIResponder, 能接收并响应事件, 负责显示内容的管理, CALayer 继承自 NSObject, 不能响应事件, 负责显示内容的绘制. 而 UIResponder 是响应者对象,实现了如下 API,所以继承自 UIResponder 的都具有响应事件的能力。

1
2
3
4
5
6
7
8
9
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

并且 UIView 提供了以下两个方法,来进行 iOS 中的事件的响应及传递(响应者链):

1
2
3
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;

- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;

UIView 中持有一个 layer 对象,同时这个 layer 对象 delegate,UIView 和 CALayer 协同工作。

平时我们对 UIView 设置 frame、center、bounds 等位置信息,其实都是 UIView 对 CALayer 进一层封装,使得我们可以很方便地设置控件的位置;例如圆角、阴影等属性, UIView 就没有进一步封装,所以我们还是需要去设置 Layer 的属性来实现功能。

UIView 持有一个 CALayer 的属性,并且是该属性的代理,用来提供一些 CALayer 行的数据,例如动画和绘制。

1
2
3
4
5
//绘制相关
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;

//动画相关
- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;

关系

UIView 主要是对显示内容的管理,而 CALayer 主要侧重显示内容的绘制。

在每一个UIView实例当中,都有一个默认的支持图层 layer,UIView 负责创建并且管理这个图层。实际上 UIView 之所以能够显示,就是因为它里面有这个一个层,才具有显示的功能 ,UIView 仅仅是对它的一层封装,实现了 CALayer 的 delegate,提供了处理事件交互的具体功能,还有动画底层方法的高级 API。可以说 CALayer 是 UIView 的内部实现细节。

我理解的 UIView 和 CALayer 之间的关系其实就是 MVC 的关系,UIView 就是 C,CALayer 就是 M,负责绘图单元的笼统来说就是 V。
当要显示像素到屏幕上,大概是这么工作的:

  • 当绘图单元需要绘制 CALayer 的时候,会拿到被标记为需要绘制的 CALayer 渲染树的值,以及要显示图片,进行像素合成。

  • CALayer 自身有个 delegate,设置的是 UIView, 当CALayer 被绘制时会执行 delegate 方法通知 UIView,看看UIView 是有提供需要绘制的元素。

  • 如果 UIView 什么都不需要提供,就当作无视。

当修改代码之后,他们大概是这么工作的:

在 UIView 和 CALayer 分别重写了父类的方法:

1
2
[UIView drawRect:rect]//UIView    
[CALayer display]//CALayer

然后,在上面两个方法加了断点,可以看到如下的执行:

区别

总的来说就是以下几点:

  • 每个 UIView 内部都有一个 CALayer 在背后提供内容的绘制和显示,并且 UIView 的尺寸样式都由内部的 layer 所提供。两者都有树状层级结构,layer 内部有 SubLayers,View 内部有 SubViews. 但是 layer 比 View 多了个 AnchorPoint;

  • 一个 Layer 的 frame 是由它的 anchorPoint, position, bounds 和 transform 共同决定的,而一个 View 的 frame 只是简单的返回 Layer 的 frame;

  • 在做 iOS 动画的时候,修改非 RootLayer 的属性(譬如位置、背景色等)会默认产生隐式动画,而修改UIView 则不会;

  • 在 View 显示的时候,UIView 做为 layer 的 CALayerDelegate, View 的显示内容由内部的 CALayer 的 display;

  • CALayer 是默认修改属性支持隐式动画的,在给 UIView 的 layer 做动画的时候,View 作为 layer 的代理,layer 通过 actionForLayer:forKey: 向 View 请求相应的 action(动画行为)
;
    一个 Layer 的 frame 是由它的 anchorPoint、position、bounds 和 transform 共同决定的,而一个 View 的 frame 只是简单的返回 Layer 的 frame;

  • 两者最明显的区别是 View可以接受并处理事件,而 Layer 不可以
。