CALayer的寄宿图

寄宿图

我们都知道,视图可以导入图片,并设置其拉伸模式、放大比例之类。而图层也可以导入图片,CALayer提供了寄宿图的概念来实现这一点。简单来说,寄宿图就是图层之中包含的图。

CALayer的contents属性

在设置图层图片时,需要给contents赋值。其定义如下:
@property(nullable, strong) id contents;
可以看到,它是一个id类型的对象,但是这是为了兼容OSX开发中CoreAnimation、AppKit中的CGImage和NSImage两种类型而设置。对于UIKit的UIImage并不支持,设置了无法识别的对象是不能显示的,所以使用UIImage需要调用下述的转换方法:
- (nullable CGImageRef)CGImage
由于转换的是CGImageRef,我们知道C类型是不受ARC管理的,所以需要进行类型桥接。最终就是如下效果:
layer.contents = (__bridge id)image.CGImage;

contents相关属性

设置了寄宿图,我们还需要对其进行参数调整。图层在样式和动画方面提供了比视图更多的选择:

  • contentGravity  // 拉伸模式
  • contentsScale   // 像素比例
  • maskToBounds   // 是否裁切
  • contentsRect    // 显示区域
  • contentsCenter   // 拉伸区域

contentsGravity

当我们为content设置了图片后,图片默认是会拉伸至图层大小的,而如同视图的contetMode,图层为我们提供了contentGravity属性。
@property(copy) NSString *contentsGravity;
它是NSString类型,可选的常量值为kCAGravity**,具体如下

  • kCAGravityCenter
  • kCAGravityTop
  • kCAGravityBottom
  • kCAGravityLeft
  • kCAGravityRight
  • kCAGravityTopLeft
  • kCAGravityTopRight
  • kCAGravityBottomLeft
  • kCAGravityBottomRight
  • kCAGravityResize
  • kCAGravityResizeAspect
  • kCAGravityResizeAspectFill

对于图片被拉伸的问题,我们处理之后如下:
contentsGravity解决图片拉伸问题

contentsScale

由于苹果设备多采用了Retina屏幕,屏幕的实际分辨率(手机像素)和设计分辨率(视图的点)之间存在着区别。比如对于iPhone 6,视图大小为375×677(单位:点),但实际像素为750×1334(单位:像素),在界面开发时并无大碍,而对于图片素材,我们有时候不希望高分屏与图片的像素绘制比例为1:1,为了不让图片换算成视图大小后太小,就要设置实际缩放比,从而让高分屏用多个像素显示图片1个像素的内容。苹果为我们提供了contentsScale属性来设置。

比如当我们设置contentsScale为1.0,即屏幕每一个点绘制图片的1个像素。而contentsScale为2.0时,屏幕每一个点绘制图片的2个像素。显示效果如下:

contentsScale区别

Tips: 需要注意,缩放只有在图片适配模式不是kCAGravityResize*(拉伸)模式时才有效。

maskToBounds

这个属性对应视图的clipToBounds属性。默认情况下视图和图层都允许子视图和子图层的绘制范围超出自己的边界。但是很多情况下我们并不想要这个效果,很多样式都需要裁切子图层。效果如下:

masksToBounds效果

contentsRect

contentsRect用来指定内容显示的区域,比如显示图片的某个局部细节。注意数值是比例大小(0.0~1.0),而非视图大小。
其只是设置显示区域,而非裁剪图片,内存中保存的依旧是整个图片,如果要处理图片应使用Core Graphic。

contentsCenter

UIImage提供了resizableImageWithCapInsets方法来配置图片的拉伸,而在图层中可以通过设置ccontentsCenter属性来设置一个可拉伸区域,从而保证图片在收到拉伸时局部不变形。

我们选择一张聊天气泡的照片,然后编写如下代码来测试:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)viewDidLoad {
[super viewDidLoad];

self.view.backgroundColor = [UIColor lightGrayColor];

UIImage *image = [UIImage imageNamed:@"bubble"];

self.layerView1.layer.contents = (__bridge id)image.CGImage;
self.layerView2.layer.contents = (__bridge id)image.CGImage;

self.layerView2.layer.contentsCenter = CGRectMake(0.75, 0.75, 0.1, 0.1);
}

原图及裁切效果为:
气泡图

裁切后

通过Core Graphic绘制

CALayer除了通过Core Animation框架的属性来设置外,还可以直接通过Core Graphic来绘制。

UIView的显示机制

当视图在屏幕上出现时,它会检测-(void)rawRect:是否被实现(该方法无默认实现),如果实现了则为视图创建一个寄宿图,其大小等于视图大小×contentsScale。(若不实现该方法则不会创建,进而优化性能)。

-(void)drawRect:是视图的方法而不是图层的,图层提供了图层方法的代理,可由视图通过实现代理方法完成绘图。-(void)drawRect:不能手动调用,需要调用-(void)setNeedsDisplay来通知重绘。

调用-(void)setNeedsDisplay后会出发代理方法-(void)displayLayer:(CALayer )layer和-(void)drawLayer:(CALayer )layer inContext:(CGContextRef)ctx。 (注:如果两种方法同时存在,则只会调用后者)。