UIView和CALayer

185 阅读4分钟

前言

iOS系统中,UIView是系统所有界面元素的基础,所有的界面元素都继承自它.真正绘图的部分是由CALayer来实现, UIView相当于CALayer的管理者 一个UIView上可以由nCALayer,每个CALayer显示一种东西,增强UIView的展现能力

1. 响应事件

首先从继承关系来看:
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate>

@interface CALayer : NSObject <NSSecureCoding, CAMediaTiming>

UIView继承自UIResponder, CALayer继承自NSObject.UIKit使用UIResponder作为响应对象,来响应系统传递的事件并进行处理. 所以 UIView可以响应事件 , 而 CALayer不具备响应事件的能力

处理触摸事件的接口:
- (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;
UIView中提供了以下两个方法,来进行iOS中事件的响应和传递
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;   // default returns YES if point is in bounds

2. init&Frame

两个类的初始化方法打断点,调用结果如下:

  • CALayerUIView创建的调用顺序

    1. UIView创建的时候调用了[MLayer init]
    2. 调用[UIView _createLayerWithFrame]创建CALayer
  • CALayerUIView设置Frame的顺序

代码执行顺序:

 [MLayer setBounds:];
 [MView setFrame:];
 [MLayer setFrame:];
 [MLayer setPosetion:];
 [MLayer setBounds:];
  1. 调用顺序可以发现创建过程中,只调用了CALayer的设置尺寸和位置而没有调用UIViewSetCenterSetBounds方法
  2. 修改UIViewbounds.size或者bounds.origin的时候也只会调用上边CALayer的一些方法
  3. 猜测UIViewCenterBounds只是直接返回CALayer对应的PositionBounds

3. 内容管理

重写UIViewdrawRect方法和CALayerdisplay,通过断点得到以下调用顺序:

  • 可以看到UIViewCALayerCALayerDelegate(继承关系里可以看到UIView遵守了CALayerDelegate)
  • 推测:代理方法内部UIView(CALayerDelegate) drawLayer:inContext]调用了UIViewdrawRect方法,从而绘制了UIView的内容

小结: UIView主要是对显示内容的管理而CALayer主要侧重显示内容的绘制

4. 隐式动画

  • 每个view都有一个layer,但是也有一些不依附view单独存在的layer,如CAShapelayer.它们不需要附加到view上就可以在屏幕上显示内容.
  • 基本上你改变一个单独的layer的任何属性的时候,都会触发一个从旧的值过渡到新值的简单动画(这就是所谓的隐式动画)
  • 如果你改变的是viewlayer的同一个属性,它只会从这一帧直接跳变到下一帧。尽管两种情况中都有layer,但是当layer附加在view 上时,它的默认的隐式动画的layer行为就不起作用了

在 Core Animation 编程指南的 “How to Animate Layer-Backed Views” 中,对为什么会这样做出了一个解释:

  • UIView默认情况下禁止了layer动画,但是在animation block中又重新启用了它们
  • 任何可动画的layer属性改变时,layer都会寻找并运行合适的action来实行这个改变.在Core Animation的专业术语中就把这样的动画统称为动作 (action或者CAAction)
  • layer通过向它的delegate发送actionForLayer:forKey:消息来询问提供一个对应属性变化的action.delegate可以通过返回以下三者之一来进行响应:
    • 它可以返回一个动作对象,这种情况下layer将使用这个动作.
    • 它可以返回一个nil,这样layer就会到其他地方继续寻找
    • 它可以返回一个NSNull对象,告诉layer这里不需要执行一个动作,搜索也会就此停止
  • layer在背后支持一个view的时候,view就是它的delegate

5. 单一原则

看到这里我们发现,UIViewCALayer之间就是MVC关系:

  • M: CALayer它的职责负责存储数据
  • C: UIView它的职责就是对layer进行管理
  • V: 负责绘图单元

总结

  1. UIView负责用户的响应操作,CALayer负责绘制

  2. 每个UIView内部都有一个CALayer在背后提供内容的显示和绘制, UIView的尺寸和样式都由内部的Layer提供

  3. CALayer是默认修改属性支持隐式动画的,在给UIViewLayer做动画的时候,View作为Layer的代理,Layer通过actionForLayer:forKey:View请求相应的action(动画行为)

  4. layer内部维护着三分layer tree,分别是presentLayer Tree(动画树),modeLayer Tree模型树),Render Tree(渲染树),在做iOS动画的时候,我们修改动画的属性,在动画的其实是LayerpresentLayer的属性值,而最终展示在界面上的其实是提供View的modelLayer`

  5. 单一职责的设计