前言
在 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];
}
在两个类的初始化方法中分别打断点然后调用,结果如下:
我们发现在创建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方法中打断点并调用得到如下结果:
可以看到 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:
消息来询问提供一个对应属性变化的 action
。delegate
可以通过返回以下三者之一来进行响应:
- 它可以返回一个动作对象,这种情况下
layer
将使用这个动作。 - 它可以返回一个
nil
, 这样layer
就会到其他地方继续寻找。 - 它可以返回一个
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 不可以