一、contents属性
CALayer有一个属性叫做contents,这个属性的类型被定义为id,意思是可以是任何类型的对象。但是如果contents赋的不是CGImage,那么图层将是空白的。
事实上,你真正要赋值的类型应该是CGImageRef,它是一个只想CGImage的结构的指针。UIImage有一个CGImage属性,它返回一个“CGImageRef“,如果把这个值赋值给CALayer的contents,将会出现编译错误,因为CGImageRef并不是一个真正的Cocoa对象,而是一个Core Foundation类型。
尽管Core Foundation类型跟Cocoa对象在运行时很像(被称作toll-free-bridging),他们并不是类型兼容的,不过你可以通过bridged关键字转换。
layer.contents = (__bridge id)image.CGImage;
这样我们就可以利用CALayer在一个普通的UIView中显示了一张图片,这不是一个UIImageView,它不是我们通常用来展示图片的方法。
1、contentGravity
类似UIView的属性contentMode,CALayer与contentMode对应的属性叫做contentGravity,但是它是一个NSString类型,而不是像对应的UIKit部分,那里面的值是枚举。contentGravity可选的常量值有以下一些:
- kCAGravityCenter
- kCAGravityTop
- kCAGravityBottom
- kCAGravityLeft
- kCAGravityRight
- kCAGravityTopLeft
- kCAGravityTopRight
- kCAGravityBottomLeft
- kCAGravityBottomRight
- kCAGravityResize
- kCAGravityResizeAspect
- kCAGravityResizeAspectFill
和contentMode一样,contentsGravity的目的是为了决定内容在图层的边界中怎么对齐。
由于图层坐标系x轴从左向右为正方向,y轴从上向下为正方向,所以Top在屏幕显示的下方。
2、contentsScale
contentsScale属性定义了寄宿图的像素尺寸和视图大小的比例,默认情况下它是一个值为1.0的浮点数。
如果contentsScale设置为1.0,将会以每个点1个像素绘制图片,如果设置为2.0,则会以每个点2个像素绘制图片,这就是Retina屏幕。
UIView有一个类似的功能但是非常少用到的contentScaleFactor属性。
当用代码的方式处理寄宿图的时候,一定记住要手动的设置图层的contentsScale属性,否则,你的图片在Retina设备上就显示的不正确啦。代码如下:
layer.contentsScale = [UIScreen mainScreen].scale;
3、maskToBounds
默认情况下,UIView仍然会绘制超过边界的内容或者子视图,在CALayer下也是这样。
UIView有一个clipToBounds属性可以用来决定是否显示超出边界的内容,同样CALayer对应的属性叫做maskToBounds,把它设置为YES,也可以达到同样效果。
4、contentsRect
CALayer的contentsRect属性允许我们在图层边框里显示寄宿图的一个子域。
和Bounds和Frame不同,contentsRect不是按点来计算的,它使用了单位坐标,单位坐标指定在0到1之间,是一个相对值。
默认的contentsRect是{0, 0, 1, 1},这以为这真个寄宿图默认都是可见的,如果制定一个小一点的矩形,图片就会被裁剪。
5、contentsCenter(九宫格)
contentsCenter其实是一个CGRect,它定义了一个固定的边框和一个在图层上可拉伸的区域。改变contentsCenter的值并不会影响到寄宿图的显示,除非图层的大小改变了,才看到效果。
这其实是游戏开发中经常谈到的九宫格视图,它其实也使用了单位坐标。
在Interface Builder里的Stretching表示contentsCenter属性,这样就不需要写代码控制了。
二、Custom Drawing
给Contents赋CGImage的值不是唯一的设置寄宿图的方法,我们也可以直接用Core Graphics直接绘制寄宿图。能够通过继承UIView并实现-drawRect: 方法来自定义绘制。
-drawRect: 方法没有默认的实现,因为对UIView来说,寄宿图并不是必须的,它不在意那到底是单调的颜色还是有一个图片的实例。如果UIView检测到-drawRect:方法被调用了,它就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以contentsScale的值。
如果你不需要寄宿图,那就不要创建这个方法了,这会造成CPU资源和内存的浪费,这就是为什么苹果建议:如果没有自定义绘制的任务就不要在子类中写一个空-drawRect:方法。
CALayer有一个可选的delegate属性,实现了CALayerDelegate协议,当CALayer需要一个内容特定的信息时,就会从协议中请求。CALayerDelegate是一个非正式协议,其实就是说没有CALayerDelegate @protocol可以让你在类里面引用啦。
当需要重绘时,CALayer会请求它的代理给他一个寄宿图来显示,通过调用以下方法:
- (void)displayLayer:(CALayer *)layer;
这里代理就可以直接设置contents属性了。如果代理不实现displayLayer方法,CALayer就会转而尝试调用下面这个方法:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
在调用这个方法之前,CALayer创建了一个合适尺寸的空寄宿图和一个Core Graphics的绘制上下文环境,为绘制寄宿图做准备,他作为ctx参数传入。
- 在layer上显式调用了-display。不同于UIView,当图层显示在屏幕上时,CALayer不会自动重绘它的内容。它把重绘的决定权交给了开发者。
- 尽管我们没使用masksToBounds属性,绘制的那个圆仍然沿边界被裁剪了。这是因为当你使用CALayerDelegate绘制寄宿图的时候,并没有对超出边界外的内容提供绘制。
当创建UIView时,UIView创建了它的寄宿图是,它会自动把图层的delegate设置为自己,UIView为我们做了以下所有的事情。