iOS 图层知识
图层与视图
UIView
在iOS中,所有的视图都是从UIView
这个基类派生而来的,UIView
可以处理用户触摸事件,可以支持基于Core Graphics绘图,可以支持仿射变换(旋转、缩放等),可以支持类似于滑动、渐变等动画。
CALayer
CALayer在概念上同UIView有些类似,同样也是被层级关系树
管理的矩形块,也可以包含一些内容(图片、文字、背景色),也可以管理子图层,有一些方法和属性用来做动画和变换。
和UIView最大的不同是:CALayer不能处理用户的交互。
图层不清楚具体的事件响应链(稍后会有文章详细讲解),因此它不能响应事件。即使它提供了一些判断某个触摸点是否在图层范围内。
平行的层级关系
1、每一个视图都有一个CALayer实例的图层属性,视图的职责就是:创建并管理这个图层,以确保子视图在视图的层级关系中被添加或者移除时,对应的图层在“层级关系树中” 有相同的操作。
2、实际上真正在屏幕上显示和做动画的是CALayer图层,UIView 仅仅是
对CALayer的封装
,提供了一些iOS类似处理触摸等事件的功能,以及Core Animation底层方法的高级接口。3、既然说CALayer是UIView内部的实现细节,那我们是否没有必要理解它?
某种意义上讲的确是这样的,对于一些简单的需求来说,我们确实没有必要处理CALayer,通过UIView提供的API即可完成,假如我们需要从底层上做一些改变,那么我们只能选择去了解CALayer.
UIView没有暴露出来的CALayer功能:
1、阴影、圆角、带颜色的边框
2、3D变换
3、非矩形范围
4、透明遮罩
5、多级非线性动画
那为什么要基于UIView和CALayer提供两个平行的层级关系?
职责分离-
CALayer 、UIView的继承关系:
1、CALayer直接继承于NSObject
2、UIView继承于UIResponder类,UIResponder中定义了处理各种事件和事件传递的接口。
继承关系图.png
UIView创建流程(UIView、CALayer的组合关系)
1、先说一下UIView创建过程:当我们实例化一个view时,UIView执行了initWithFrame:后,内部会调用私有方法_createLayerWithFrame:
来创建CALayer图层。
下面是获取到的调用栈:
注:这里我们重写了view的类方法:layerClass,使其返回我们自定义的CALayer。
2、之后我们看到系统会去调用CALayer的setFrame:、setPosition:、setBounds:
去设置图层最终的frame
。
调用栈如下:
也就是说:我们在创建view时设置的frame,最终转化成了给view的layer设置frame和position。
我们总结下调用过程如下:
[UIView _createLayerWithFrame]
[Layer setBounds:bounds]
[Layer setFrame:Frame]
[Layer setPosition:position]
[Layer setBounds:bounds]
同理,我们可以理解UIView的frame 获取其实是对CALayer frame的简单封装而已。本质返回的还是CALayer的frame值。
3、上面是布局,接下来要进行绘制,我们看到 UIView调用drawRect:进行绘图的操作,实际上是由CALayer通过CALayerDelegate在代理方法内部调用的drawRect:
,说明真正的内容绘制操作还是由CALayer来负责。
调用栈如下:
简单分析下:
当UIView需要绘制显示时,它内部的层会准备好一个CGContextRef(图形上下文),然后调用delegate(这里就是UIView)的drawLayer:inContext:方法,并且传入已经准备好的CGContextRef对象。而UIView在drawLayer:inContext:这个代理方法中又会调用自己的drawRect:方法。
这里引出一个CGContextRef获取的问题
,有过经验的开发都知道:
当我们通过core Graphic绘图时, 在使用UIGraphicsGetCurrentContext()时,只有在drawRect:中获取上下文才是有效的。而在其他地方获取是nil。
原因:系统会维护一个CGContextRef的栈,UIGraphicsGetCurrentContext()会取出栈顶的context,只有Layer调用了它的代理方法时(也就是上面的drawLayer:inContext:),才会往栈中压入一个有效的CGContextRef。
到这里是不是就清楚了之前为什么不能再其他地方获取这个context。
在drawRect中完成的绘制,也会被填入到context中,然后被提交到系统的transition队列中等待渲染,最后展示在屏幕上。
基于第三点我们可以简单的认为:CALaye 侧重内容的绘制,而UIView 侧重内容的管理和组建
4、在做 iOS 动画的时候,修改非 RootLayer的属性(譬如位置、背景色等)会默认产生隐式动画,而修改UIView则不会。
总结:1、2 步是View布局的过程,3是绘制的过程。
我们在通过代码创建了一个view后,这个view最终展示在屏幕上会有两个过程:
1、布局,也就是给view设置frame,bounds,border等 参数 ,但是这时仅仅是设置参数而已。通常这一步对应LayoutView 等函数。
2、上一步设置完参数,需要根据设置好的参数,把view绘制出来。这一步对应drawRect:函数。
续:
在验证过程中,重写了layer,这里顺道也添加上,方便大家指正问题。
@implementation CustomView
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
//这个函数对外没有公开,是UIView内部实现的代理类,我们重写该函数可以确认,UIView确实是CALayer的代理
}
+ (Class)layerClass {
return [HYLayer class];
}
@end
@implementation HYLayer
- (void)setFrame:(CGRect)frame {
[super setFrame:frame];
}
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
}
- (void)setPosition:(CGPoint)position {
[super setPosition:position];
}
- (void)display {
[super display];
}
- (void)drawInContext:(CGContextRef)ctx {
[super drawInContext:ctx];
}
@end