iOS UIView和CALayer

作者: 不简单的风度 | 来源:发表于2017-05-07 12:30 被阅读1546次

    前言

    在 iOS 中,所有的 view 都是由一个底层的 layer 来驱动的。view 和它的 layer 之间有着紧密的联系,view 其实直接从 layer 对象中获取了绝大多数它所需要的数据。layer侧重于图形的显示,而view相当于layer的管理者。本文将从几个不同方面来比较view和layer的区别和联系。

    响应事件

    UIView的定义:

    NS_CLASS_AVAILABLE_IOS(2_0) @interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, CALayerDelegate>
    

    CALayer的定义:

    CA_CLASS_AVAILABLE (10.5, 2.0, 9.0, 2.0)
    @interface CALayer : NSObject <NSCoding, CAMediaTiming>
    

    从UIView和CALayer的定义可以看出UIView是继承于UIResponder,而CALayer是继承于NSObject。在iOS中,UIKit使用UIResponder作为响应对象,来响应系统传递过来的事件并进行处理。所以UIView可以响应事件,而CALyer则不能响应事件。

    初始化和Frame

    一个 Layer 的 frame 是由它的 anchorPoint,position,bounds,和 transform 共同决定的,而一个 View 的 frame 只是简单的返回 Layer的 frame,同样 View 的 center和 bounds 也是返回 Layer 的一些属性。我在另一篇文章中看到笔者自定义了view和layer做了实验来做测试,为了一探究竟我也做了类似的测试。
    自定义两个类MMView和MMLayer分别集成UIView和CALyer。
    在MMView中重写以下方法:

    - (instancetype)init{
        self = [super init];
        if (self) {
            NSLog(@">>>>>>>>>>> MMView init");
        }
        return self;
    }
    
    + (Class)layerClass{
        return [MMLayer class];
    }
    
    - (void)setFrame:(CGRect)frame{
        [super setFrame:frame];
    }
    
    - (void)setCenter:(CGPoint)center{
        [super setCenter:center];
    }
    
    - (void)setBounds:(CGRect)bounds{
        [super setBounds:bounds];
    }
    

    在MMLayer中重写以下方法:

    - (instancetype)init{
        self = [super init];
        if (self) {
            NSLog(@">>>>>>>>>>> MMLayer init");
        }
        return self;
    }
    
    + (Class)layerClass{
        return [MMLayer class];
    }
    
    - (void)setFrame:(CGRect)frame{
        [super setFrame:frame];
    }
    
    - (void)setPosition:(CGPoint)position{
        [super setPosition:position];
    }
    
    - (void)setBounds:(CGRect)bounds{
        [super setBounds:bounds];
    }
    

    在两个类的初始化方法中分别打断点然后调用,结果如下:

    BreakPoint.png

    我们发现在创建view的时候会先调用- [MMLayer init],然后调用- [UIView _createLayerWithFrame]来创建layer。

    如果在创建 View 的时候,在 Layer 和 View 中Frame 相关的所有方法中都加上断点,可以看到大致如下的调用顺序如下:

     [MMLayer setBounds:];
     [MMView setFrame:];
     [MMLayer setFrame:];
     [MMLayer setPosetion:];
     [MMLayer setBounds:];
    

    从调用顺序可以发现在创建的过程只有调用了 Layer 的设置尺寸和位置的然而并没有调用View 的 SetCenter 和 SetBounds 方法。

    如果修改了 view的 bounds.size 或者 bounds.origin 的时候也只会调用上边 Layer的一些方法。所以我们可以做如下猜测:View 的 Center 和 Bounds 只是直接返回layer 对应的 Position 和 Bounds.

    内容管理和绘制

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

    分别重写UIView的drawRect和CALayer的display方法
    在MMView中重写drawRect:

    - (void)drawRect:(CGRect)rect{
        [super drawRect:rect];
    }
    

    在MMLayer中重写display:

    - (void)display{
        [super display];
    }
    

    在drawRect方法中打断点并调用得到如下结果:

    drawRect breakpoint.png

    可以看到 UIView 是 CALayer 的CALayerDelegate,由此可以猜测是在代理方法内部[UIView(CALayerDelegate) drawLayer:inContext]调用 UIView 的drawRect方法,从而绘制出了 UIView 的内容。

    隐式动画

    每个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:消息来询问提供一个对应属性变化的 actiondelegate 可以通过返回以下三者之一来进行响应:

    1. 它可以返回一个动作对象,这种情况下 layer 将使用这个动作。
    2. 它可以返回一个 nil, 这样 layer 就会到其他地方继续寻找。
    3. 它可以返回一个 NSNull 对象,告诉layer 这里不需要执行一个动作,搜索也会就此停止。

    layer在背后支持一个view 的时候,view 就是它的 delegate

    总结

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

    • 在 View显示的时候,UIView 做为 Layer 的CALayerDelegate,View 的显示内容取决于内部的 CALayer 的 display

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

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

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

    参考

    详解 CALayer 和 UIView 的区别和联系

    相关文章

      网友评论

        本文标题:iOS UIView和CALayer

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