美文网首页
iOS 图层知识

iOS 图层知识

作者: 好雨知时节浩宇 | 来源:发表于2017-12-23 15:47 被阅读25次

    图层与视图

    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图层。
    下面是获取到的调用栈:

    截图1.png

    注:这里我们重写了view的类方法:layerClass,使其返回我们自定义的CALayer。

    2、之后我们看到系统会去调用CALayer的setFrame:、setPosition:、setBounds:去设置图层最终的frame
    调用栈如下:

    屏幕快照 2017-12-22 下午5.52.57.png

    也就是说:我们在创建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来负责。
    调用栈如下:

    屏幕快照 2018-07-01 17.54.30.png

    简单分析下:
    当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
    

    相关文章

      网友评论

          本文标题:iOS 图层知识

          本文链接:https://www.haomeiwen.com/subject/hxvpwxtx.html