Layers
Layers
- A UIView does not actually draw itself onto the screen; it draws itself into its layer, and it is the layer that is portrayed on the screen.
- a view is not redrawn frequently;
- instead, its drawing is cached, and the cached version of the drawing (
the bitmap backing store) is used where possible. - The cached version is, in fact, the
layer. - the view’s graphics context is
actuallythe layer’s graphics context. - a layer is the
recipientandpresenterof a view’s drawing - Layers are
made to be animated - View持有layer,是layer的代理(
CALayerDeletgate)- 但layer不能找到View
- View的大部分属性都只是其
underlying layer的便捷方法 - layer能操控和改变view的表现,而无需ask the view to redraw itself
自定义underlaying layer的方法
class CompassView : UIView {
override class var layerClass : AnyClass {
return CompassLayer.self
}
}
Layers and Sublayers
-
layer的继承树跟view的继承树几乎一致
-
layer的
maskToBounds属性决定了能否显示sublayer超出了其bounds的部分,这也是view的clipsToBounds的平行属性 -
sublayers是可写的,而subviews不是- 所以设为nil可以移除所有子层,但subview却需要一个个
removeFromSuperview
- 所以设为nil可以移除所有子层,但subview却需要一个个
-
zPostion决定了层级(order),默认值都是0.0 -
a layer does not have a center靠
position和anchorPoint定位- position: 在superLayer中的位置
- anchorPoint: 用小数表示的bound(宽/高)位置,左上(0, 0), 右下(1, 1), default:(0.5, 0.5)
- 所以(0.5, 0.5)的anchorPoint,对应的
poosition就等同于center了,理解一下- 其实就是说你的“锚点”在superLayer的什么位置的意思
- When you get the frame, it is calculated from the
bounds sizealong with thepositionandanchorPoint.- When you set the frame, you set the
boundssize andposition
- When you set the frame, you set the
// demo, 把一个80x40的layer,左上角放到(130, 120的位置)
let layer = CALayer()
layer.bounds = CGRect.init(x: 0, y: 0, width: 80, height: 40)
layer.backgroundColor = UIColor.yellow.cgColor
layer.position = CGPoint.init(x: 130, y: 120)
layer.anchorPoint = CGPoint.init(x: 0, y: 0)
如果一个layer的position是(0, 0),锚点是(0,0),刚好显示在左上角 而(0.5,0.5)则只能显示右下角的1/4了 即(0.5, 0.5)到了原来(0,0)的位置。所以说其实就是把自身bounds度量下的哪个位置移到(0,0)
这么说来,对锚点的最正确理解其实是,
- 我把自身坐标系里的哪个点定义为原点,
- 并且,这个点移到原本“左上角”的位置(想象0.5,0.5)
- 并且,所有的旋转之类的动画本来是对“左上角”的位置进行的,不管现在这个位置是layer上的哪个部分
- 或者说,旋转永远是发生在
position上的,你把哪个点放到position上它不管
- 或者说,旋转永远是发生在
理解frame的小练习
// 如果我设了layer的frame:
circle.frame = CGRect.init(x: 50, y: 50, width: 200, height: 200)
// 实际上是通过size, position, anchorPoint来实现的:
circle.bounds = CGRect(x: 0, y: 0, width: 200, height: 200)
// 以左上角为anchorPoint
circle.position = CGPoint(x:50, y:50)
circle.anchorPoint = CGPoint(x:0, y:0)
// 或者,以中心为anchorPoint
circle.position = CGPoint(x:150, y:150)
circle.anchorPoint = CGPoint(x:0.5, y:0.5)
// 或者其它任意anchorPoint,前提是自己换算
// 而且,虽然位置是一样的,但会影响transform
CAScrollLayer
- 你想通过移动layer的bounds来重定位sublayers,可以使用
CAScrollLayer - 但是它并不能通过拖拽来移动里面的内容(记得它没有响应链)
- 而是理解为一个
masksToBounds的窗口,你只能看到它bounds里面的内容 - 能通过本身的
scroll(to:)方法,和sublayers的scroll(_:)和scrollRectToVisible(_:)方法来改变scroll layer的bounds,达到显示sublayer指定区域的目的
Layer and Delegate
- 对一个不是UIView的undrelying layer的layer,让(任意)一个对象成为其delegate,可以由它来操控它的layout和drawing
- 但千万不要让UIView成为不是其underlying的layer的代理,反之亦然
Layout of Lyaers
- When a layer needs layout, either because its bounds have changed or because you called
setNeedsLayout
Drawing in a Layer
- set
contentsis the simplest way to draw in a layer ->CGImage- 但
contents能接受任何类型,所以不正确的content只会fail silently
- 但
- layer也有一个
draw(_:)方法,它被(自动)调用的时候通常表示要redisplay itself`,什么时候需要redisplay itself?- 如果
needsDisplayOnBoundsChange是false,那么就只有在sefNeedDisplay方法(及其inRect衍生方法)里会触发- 如果是非常重要的重绘,那么需要再显式调用一次
displayIfNeeded
- 如果是非常重要的重绘,那么需要再显式调用一次
- 是true的话就如其名,在bounds变化的时候也会重绘
- 如果
- 有四个方法能在redisplay的时候调用:
- subclass的
display重载,它没有graphics context,所以只能提供图片 - delegate的
display(in:)方法,同样,只能提供图片 - subclass的
draw(in:)方法,有context,所以能直接在里面绘图,但不会make current context - delegate的
draw(_:in)方法,限制也同上
- subclass的
- underlaying layer不应调用上面的方法,而交由view的
draw(_:)方法- 一定要调也可以,但要显式实现view的
draw(_:)方法,方法体为空就行了
- 一定要调也可以,但要显式实现view的
Drawing-Related Layer Properties
- contentsScale: 像素对高分屏的映射,Cocoa管理的layer会自动设置,自定义的类需要注意这个scale
- opacity: 就是view的
alpha- Changing the isOpaque property has no effect until the layer redisplays itself.
Content Resizing and Positioning
- A layer’s content is stored (cached) as a bitmap which is then treated like an image:
- 如果content来自一张图片,那么缓存的就是图片(CGImage),大小就是图片的point size
- 如果来自绘图,那么存的是graphics context
ContentGravity,类似UIView’s contentMode property,即缩放拉伸- 因为坐标系不同的历史原因,top, bottom是相反的
- 如果是自己绘制,则这个属性无意义,但结合下面的rect属性又有用了,因为截取了rect大小的绘制
contentsRect,结合上一个属性,做购物网站那种截取一小块,绘制到一个大图上去。这里是绘制到view上- 默认是全图(0,0,1,1)
contentsCenter?? 好像是对上述rect属性划成9宫格,不同位置的格子缩放规则不一样,比如四个角落的格子,不会缩放- 所以给了一个center region(rect),把它的四条边延长,就有9个格子了
Layers that Draw Themselves
系统内置了一些能自我绘制的layer:
- CATextLayer,轻量版的UILabel。通过
string属性存取,与contenta会冲突,不要同时设。 - CAShapeLayer, 有path属性,可以与
contents共存,path绘制于content之上,并且不能设融合模式 - CAGradientLayer,通过背景色做的渐变,去了解下clip和mask
Transforms
- view的transform是根据其center来应用的,layer的是根据
anchorPoint- 所以
anchorPoint就两个作用,把它移动到position的位置,和以它为中心进行旋转
- 所以
- 画刻度,核心是把文字先往上挪到圆圈的位置,所以anchorPoint只动y不动x (center, midY/textHeight)
let str = "ABCD"
for (i, s) in str.enumerated() {
let t = CATextLayer()
t.string = String(s)
t.bounds = CGRect.init(x: 0, y: 0, width: 40, height: 40)
t.position = circle.center // 这才是核心,一切定位和旋转的基准
let vert = circle.bounds.midY/t.bounds.height
t.anchorPoint = CGPoint.init(x: 0.5, y: vert) // 半圆是文字调蓄的多少倍,就上移多少,但隐形的脚(即高跷的支点)仍在position处
t.foregroundColor = UIColor.red.cgColor
t.setAffineTransform(CGAffineTransform(rotationAngle: .pi/2.0 * CGFloat(i)))
circle.addSublayer(t)
}
结果如图:

- 画箭头,演示了复杂的绘制怎么把它代理出去,并且什么时机让它产生绘制:
// the arrow
let arrow = CALayer()
arrow.contentsScale = UIScreen.main.scale
arrow.bounds = CGRect(0, 0, 40, 100)
arrow.position = self.bounds.center
arrow.anchorPoint = CGPoint(0.5, 0.8) // 箭尾凹进去的位置(所以不可能是1.0)
arrow.delegate = self // we will draw the arrow in the delegate method
arrow.setAffineTransform(CGAffineTransform(rotationAngle:.pi/5.0))
self.addSublayer(arrow)
arrow.setNeedsDisplay() // draw, please
** 3D Transforms
- A layer’s
affineTransformis merely a façade for accessing itstransform. - A layer’s
transformis a three-dimensional transform, aCATransform3D
绕Y轴镜像的示例:
someLayer.transform = CATransform3DMakeRotation(.pi, 0, 1, 0)
一般而言,在Z轴没有分量的平面图,那就只剩旋转的效果了(没有翻转)

这是把anchorPoint设在了圆心,如果设在(0,0):

- layer不是为了3D建模而诞生的(考虑
Metal),它是2D对象,为speed和simplicity而设计
depth
现实世界z-component的加入会近大远小,layer绘制没有表现出这种距离,而是压平到一个面:orthographic projection,但是使用了一些技巧来制造这种视觉效果。