contents属性
CALayer有一个属性叫做contents,这个属性的类型被定义为AnyObject?,意味着它可以是可空的任意类型对象。在这种情况下,可以给contents属性赋任何值,你的App仍然能够编译通过。但是在实践中,如果给contents赋的不是CGImage,那么图层将是空白的。
contents属性类型之所以被定义为AnyObject?,是因为在Mac OS系统上,这个属性对CGImage和NSImage类型的值都起作用。如果在iOS平台上将UIImage的值赋给它,只能得到一个空白的图层。
基于上一个DemoCode,我们这次直接把layerView的宿主图层的contents属性设置成图片
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var layerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let image = UIImage(named: "snowman")
self.layerView.layer.contents = image?.CGImage
}
}
contentGravity属性
在使用UIImageView的时候遇到过图片被拉伸的情况,解决方案就是把contentMode属性设置成某个合适的值,例如:
imageView.contentMode = .ScaleAspectFit
CALayer与contentMode对应的属性叫做contentsGravity,但是它是一个String类型,而不是一个枚举类型。contentsGravity可选的常用值有以下这些:
- kCAGravityCenter
- kCAGravityTop
- kCAGravityBottom
- kCAGravityLeft
- kCAGravityRight
- kCAGravityTopLeft
- kCAGravityTopRight
- kCAGravityBottomLeft
- kCAGravityBottomRight
- kCAGravityResize
- kCAGravityResizeAspect
- kCAGravityResizeAspectFill
和contentMode一样,contentsGravity的目的是为了决定内容在图层的边界中怎样对齐,假如使用kCAGravityResizeAspect,它的效果等同于UIViewContentModeScaleAspectFit,同时它还能在图层中等比例拉伸以适应图层的边界。
self.layerView.layer.contentsGravity = kCAGravityResizeAspect
contentScale属性
contentScale属性定义了寄宿图的像素尺寸和视图大小的比例,默认情况下它是一个值为1.0的浮点数。
contentScale属性其实属于支持高分辨率屏幕机制的一部分。它用来判断在绘制图层的时候应该为寄宿图创建的空间大小和需要显示的图片的拉伸度。
如果contentScale值为1.0,将会以每一个点1个像素绘制图片,如果设置为2.0,则会以每个点2个像素绘制图片,就是熟知的Retina屏幕。
当用代码的方式来处理寄宿图时候,一定要记住要手动的设置图层的contentScale属性,否则图片在Retina设备上就显得不正确。
self.layerView.layer.contentsScale = UIScreen.mainScreen().scale
masksToBounds属性
UIView有一个叫clipToBounds的属性,可以用来决定是否显示超出边界的内容,CALayer对应的属性叫做masksToBounds,设置值为true,内容就不会超出边界了。
self.layerView.layer.masksToBounds = true
contentsRect属性
CALayer的contentsRect属性允许在图层边框里显示寄宿图的一个子域。
和bounds、frame不同,contentsRect不是按照点来计算的,它使用的是单位坐标,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)。
iOS使用了以下的坐标系统
- 点:在iOS和Mac OS中最常见的坐标体系。点就像虚拟的像素,也被称作逻辑像素。在标准设备上,一个点就是一个像素,但是在Retina设备上,一个点等于22个像素或33个像素。iOS用点作为屏幕的坐标测算体系就是为了在Retina设备和普通设备上能有一致的视觉效果。
- 像素:物理像素坐标并不会用来屏幕布局,但是仍然与图片有相对关系。UIImage是一个屏幕分辨率解决方案,所以指定点来度量大小。但是一些底层的图片表示,如CGImage就会使用像素,所以我们要清楚在Retina设备和普通设备上,它们表现出来了不同的大小。
- 单位:对于与图片大小或是图层边界相关的显示,单位坐标是一个方便的度量方式,当大小改变的时候,也不需要再次调整。单位坐标在OpenGL这种纹理坐标系统中用的很多,Core Animation中也用到了单位坐标。
默认的contentsRect值是{0,0,1,1},意味着整个寄宿图默认都是可见的。
事实上给contentsRect设置一个负数的原点或是大于{1,1}的尺寸也可以。这种情况下,最外面的像素会被拉伸以填充剩下的区域。
下面加入一些代码,可以只显示snowman的右上角四分之一的内容,而layer的大小保持不变,所以显示的内容会被拉伸。
// 选择右上角四分之一为内容
self.layerView.layer.contentsRect = CGRectMake(0.5, 0, 0.5, 0.5)
//拉伸
self.layerView.layer.contentsGravity = kCAGravityResize
contentsCenter属性
从contentsCenter属性名字看,初学者很有可能认为它和图片的位置有关,不过这个名字误导了你。
contentsCenter其实是一个CGRect,它定义了一个固定的边框和一个在图层上可拉伸的区域。
默认情况下,contentsCenter是{0,0,1,1},这意味着如果大小(由contentsGravity决定)改变了,那么寄宿图将会均匀地拉伸。
如果contentsCenter属性是上图中间的蓝色方框,那么当这个图片被拉伸后,contentsCenter属性定义的区域会被全面拉伸(也就是从四个方向进行放大或缩小),而被这个方框分隔后的其它方格会按照上图所示的进行横向或者纵向的拉伸,或者某些方框根本不拉伸,这就是contentsCenter属性的意义。
contentsCenter属性和contentsRect属性一样,同样是以比例作为单位。两个属性可以叠加,如果contentsRect属性被设置,contentsCenter属性就会操作contentsRect属性所定义的范围。
下面加入一些代码,基于上个snowman效果,把左下角的四分之一部分进行拉伸
//左下角四分之一拉伸
self.layerView.layer.contentsCenter = CGRectMake(0, 0.5, 0.5, 0.5)
也可以在Interface Builder里配置,而不需要写代码
Custom Drawing
为contents赋CGImage值并不是唯一设置寄宿图的方法。我们也可以直接用Core Graphics直接绘制寄宿图。
下面通过代码实现CALayerDelegate来绘制图层
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var layerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// 创建子layer
let blueLayer = CALayer()
blueLayer.frame = CGRectMake(50.0, 50.0, 100.0, 100.0)
blueLayer.backgroundColor = UIColor.blueColor().CGColor
//设置layer的delegate
blueLayer.delegate = self
//确保layer的寄宿图使用正确的scale
blueLayer.contentsScale = UIScreen.mainScreen().scale
self.layerView.layer.addSublayer(blueLayer)
//强制layer重绘
blueLayer.display()
}
override func drawLayer(layer: CALayer, inContext ctx: CGContext) {
CGContextSetLineWidth(ctx, 10.0)
CGContextSetStrokeColorWithColor(ctx, UIColor.redColor().CGColor)
CGContextStrokeEllipseInRect(ctx, layer.bounds)
}
}
注意
- blueLayer上显示地调用了display()。不同于UIView,当图层显示在屏幕上时,CALayer不会自动重绘它的内容,它把重绘的决定权交给了开发者。
- 尽管这里没有设置masksToBound属性,绘制的那个圆仍然沿着边界被裁减了。这是因为当使用CALayerDelegate绘制寄宿图的时候,并没有对超出边界外的内容提供绘制支持。
最后除非你创建了一个单独的图层,你几乎没有机会用到CALayerDelegate协议。因为当UIView创建了它的寄宿图层时,它会自动地把图层的delegate设置为自己,并提供了一个displayLayer的实现。