2. The Backing Image
A picture is worth a thousand words. An interface is worth a thousand pictures.
——Ben Shneiderman
CALayer 有一个 id 类型的属性 contents,但是实际上,只有在你传入一个 CGImage 时才能起作用。其 id 类型的原因在于当它在 Mac OS 上被使用时,你既可以赋予这个属性一个 CGImage 或者 NSImage ,但是在 iOS 上,你只能赋一个 CGImage 给它,像 UIImage 这样的也不行。
实际使用 CALayer 的 contents 属性时,你需要提供的是一个 CGImageRef,也就是一个指向 CGImage 结构体的指针。UIImage 有一个叫做 CGImage 的属性,这个属性会返回一个隐含的 CGImageRef,如果你直接把这个值赋给 CALayer 的 contents 属性,编译时是会报错的,因为 CGImageRef 不是一个 Cocoa 对象,它是 Core Foundation 类型的数据。所以,这里我们需要用桥接(bridge cast)的方式来将其转换成 id 类型的数据:
layer.contents = (__bridge id)image.CGImage;
尽管我们可以通过设置 CALyer 的 contents 属性来展示图片,但是它并不是像 UIImageView 那样专门用来展示图片的。
contentsGravity 属性:类似于 UIView 的contentMode 属性,决定内容的展示位置和尺寸比例,它是一个 NSString 类型的值,我们可以从系统的 framework 中定义的字符串常量选用自己想要的值。
contentScale 属性:这个属性定义了 layer 中图片(contents)的像素尺寸与 view 的尺寸的比例,默认值为 1.0,也就是说在屏幕绘制该图形时,是按照一点(point)刚好就是1个像素(pixel)的分辨率来处理的,如果这个值为 2.0,那就代表一点中显示两个像素的内容(Retina 显示屏就是这样的分辨率)。值得注意的是,当你将 CALayer 的 contentGravity 属性设为 KCAGravityResizeAspect 时,contentScale 属性是不起作用的。UIView 也有一个类似的属性 contentScaleFactor,但是我们很少用到。最后,在操作 CALayer 的 contents 时,一定要记得手动设置 layer 的 contentsScale 属性来适配屏幕分辨率:
layer.contentScale = [UIScreen mainScreen].scale;
maskToBounds 属性:类似于 UIView 的 clipsToBounds 属性,用来裁剪超出 frame 边界的内容。
contentsRect 属性:用来在 layer 中展示图片的一部分内容。不像 bounds 和 frame 这些以点(point)为单位,contentsRect 以单元坐标(unit coordinates)为单位,取值范围从 0 到 1,是一个相对值。contentsRect 属性默认值为{0, 0, 1, 1},也就是说图片正好完整地显示在 layer 的 frame 中。
Image Sprites :对 contentsRect 属性最有意思的应用就是能够使用 image sprites 了。Sprites 一般用在像 Cocos2D 这样的 2D 游戏引擎中,通过 OpenGL 来显示图片。作者 Nick Lockwood 还写了一个关于 Sprites 的开源库:https://github.com/nicklockwood/LayerSprites。
contentCenter 属性:类似于 UIImage 的 -resizableImageWithCapInsets:方法一样,确定一块可伸缩的中间区域,在 layer 大小发生改变时,中间区域(contentCenter)自动伸缩,而四周不变。默认值是{0, 0, 1, 1}。
UIView 默认没有实现 -drawRect:方法,因为如果 UIView 只是填充了某种颜色或者它的 layer 的 contents 已经包含了一个 image 实例,这样的话 UIView 就不需要一个自定义的 backing image了;当 UIView 检测到 -drawRect:方法被实现了,系统将会为这个 view 生成一个 backing image,这个 backing image 的像素尺寸等于 view 的尺寸乘以 contentScale 。如果你不需要这个 backing image,你就最好不要实现 -drawRect:方法,因为这样会浪费内存和 CPU,这也是 Apple 为什么告诉你如果不想进行自定义绘制的话就不要留一个空的-drawRect:方法在那里的原因。
当 view 第一次出现在屏幕上时,-drawRect 方法就被自动调用了,-drawRect:方法中的自定义实现会被缓存起来,直到 view 需要更新的时候。
尽管 -drawRect:方法是 UIView 的方法,但它实际上是由隐藏在 view 背后的 layer 来管理绘制和存储图片的。CALayer 有一个遵循 CALayerDelegate 协议的 delegate 属性,当 CALayer 需要绘制信息时,它就会询问它的 delegate。CALayer 在绘制时会调用其 delegate 的两个方法:
- (void)displayLayer:(CALayerCALayer *)layer;
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
除非你单独用到了 CALyer 来绘制,大多数情况下,你根本不要实现 CALayerDelegate 协议。因为当 UIView 创建了他的附属 layer 时,会自动将把它自己设为那个 layer 的 delegate,并且实现了 -displayLayer:方法。