前言
在iOS系统中,UIView是系统所有界面元素的基础,所有的界面元素都继承自它.真正绘图的部分是由CALayer来实现, UIView相当于CALayer的管理者
一个UIView上可以由n个CALayer,每个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
两个类的初始化方法打断点,调用结果如下:
-
CALayer和UIView创建的调用顺序UIView创建的时候调用了[MLayer init]- 调用
[UIView _createLayerWithFrame]创建CALayer
-
CALayer和UIView设置Frame的顺序
代码执行顺序:
[MLayer setBounds:];
[MView setFrame:];
[MLayer setFrame:];
[MLayer setPosetion:];
[MLayer setBounds:];
- 调用顺序可以发现创建过程中,只调用了
CALayer的设置尺寸和位置而没有调用UIView的SetCenter和SetBounds方法 - 修改
UIView的bounds.size或者bounds.origin的时候也只会调用上边CALayer的一些方法 - 猜测
UIView的Center和Bounds只是直接返回CALayer对应的Position和Bounds
3. 内容管理
重写UIView的drawRect方法和CALayer的display,通过断点得到以下调用顺序:
- 可以看到
UIView是CALayer的CALayerDelegate(继承关系里可以看到UIView遵守了CALayerDelegate) - 推测:代理方法内部
UIView(CALayerDelegate) drawLayer:inContext]调用了UIView的drawRect方法,从而绘制了UIView的内容
小结: UIView主要是对显示内容的管理而CALayer主要侧重显示内容的绘制
4. 隐式动画
- 每个
view都有一个layer,但是也有一些不依附view单独存在的layer,如CAShapelayer.它们不需要附加到view上就可以在屏幕上显示内容. - 基本上你改变一个单独的
layer的任何属性的时候,都会触发一个从旧的值过渡到新值的简单动画(这就是所谓的隐式动画) - 如果你改变的是
view中layer的同一个属性,它只会从这一帧直接跳变到下一帧。尽管两种情况中都有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. 单一原则
看到这里我们发现,UIView和CALayer之间就是MVC关系:
- M:
CALayer它的职责负责存储数据 - C:
UIView它的职责就是对layer进行管理 - V: 负责绘图单元
总结
-
UIView负责用户的响应操作,CALayer负责绘制 -
每个
UIView内部都有一个CALayer在背后提供内容的显示和绘制,UIView的尺寸和样式都由内部的Layer提供 -
CALayer是默认修改属性支持隐式动画的,在给UIView的Layer做动画的时候,View作为Layer的代理,Layer通过actionForLayer:forKey:向View请求相应的action(动画行为) -
layer内部维护着三分layer tree,分别是presentLayer Tree(动画树),modeLayer Tree模型树),Render Tree(渲染树),在做iOS动画的时候,我们修改动画的属性,在动画的其实是Layer的presentLayer的属性值,而最终展示在界面上的其实是提供View的modelLayer` -
单一职责的设计