contents属性

  • CALayer 有一个属性叫做contents,这个属性的类型被定义为id,意味着它可以是任何类型的对象。可以给contents属性赋任何值,你的app仍然能够编译通过;

  • 但是,在实践中,如果你给contents赋的不是CGImage,那么你得到的图层将是空白的

思考:为什么contents 属性的类型被定义为id?

由Mac OS的历史原因造成的。它之所以被定义为id类型,是因为在Mac OS系统上,这个属性对CGImage和NSImage类型的值都起作用。如果你试图在iOS平台上将UIImage的值赋给它,只能得到一个空白的图层。

  1. contents属性的实际赋值
  • 事实上,你真正要赋值的类型应该是CGImageRef,它是一个指向CGImage结构的指针

  • UIImage有一个CGImage属性,它返回一个”CGImageRef”,如果你想把这个值直接赋值给CALayer的contents,那你将会得到一个编译错误。因为CGImageRef并不是一个真正的Cocoa对象,而是一个Core Foundation类型

  • 尽管Core Foundation类型跟Cocoa对象在运行时貌似很像(被称作toll-free bridging),他们并不是类型兼容的,不过你可以通过bridged关键字转换

给图层的寄宿图赋值的写法:

1
layer.contents = (__bridge id)image.CGImage; //使用ARC(自动引用计数)

contentGravity

在使用UIImageView的时候遇到图片拉伸问题,解决方法就是把contentMode属性设置成更合适的值

CALayer与contentMode对应的属性叫做contentsGravity,用来处理寄宿图的拉伸适配;

cotentMode一样,contentsGravity的目的是为了决定内容在图层的边界中怎么对齐

具体的设置参数包括:

  • kCAGravityCenter :中心显示
  • kCAGravityTop:上方显示
  • kCAGravityBottom:下方显示
  • kCAGravityLeft:左边显示
  • kCAGravityRight:右边显示
  • kCAGravityTopLeft
  • kCAGravityTopRight
  • kCAGravityBottomLeft
  • kCAGravityBottomRight
  • kCAGravityResize
  • kCAGravityResizeAspect
  • kCAGravityResizeAspectFill

contentsScale

  • contentsScale属性定义了寄宿图的像素尺寸和视图大小的比例,默认情况下它是一个值为1.0的浮点数;UIView有一个类似功能但是非常少用到的contentScaleFactor属性
  • contentsScale并不是总会对屏幕上的寄宿图有影响,若contents由于设置了contentsGravity`属性,则它已经被拉伸以适应图层的边界,contentsScale不会起作用(contentsScale与contentsGravity冲突);
  • contentsScale属性其实属于支持高分辨率(又称Hi-DPI或Retina)屏幕机制的一部分:用来判断在绘制图层的时候应该为寄宿图创建的空间大小,和需要显示的图片的拉伸度(假设并没有设置contentsGravity属性)
  • 如果contentsScale设置为1.0,将会以每个点1个像素绘制图片,如果设置为2.0,则会以每个点2个像素绘制图片,这就是我们熟知的Retina屏幕.
  • 当用代码的方式来处理寄宿图的时候,一定要记住要手动的设置图层的contentsScale属性,否则,你的图片在Retina设备上就显示得不正确
1
layer.contentsScale = [UIScreen mainScreen].scale;

maskToBounds

认情况下,UIView仍然会绘制超过边界的内容或是子视图,在CALayer下也是这样的

UIView有一个叫做clipsToBounds的属性可以用来决定是否显示超出边界的内容

CALayer对应的属性叫做masksToBounds(边缘遮挡),把它设置为YES


contentsRect

CALayer的contentsRect属性允许我们在图层边框里显示寄宿图的一个子域

boundsframe不同,contentsRect不是按点来计算的,它使用了单位坐标,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)。所以他们是相对与寄宿图的尺寸的

ios 中使用到的坐标系统:

  1. 点——在iOS和Mac OS中最常见的坐标体系。点就像是虚拟的像素,也被称作逻辑像素;

    ​ 在标准设备上,一个点就是一个像素,但是在Retina设备上,一个点等于2*2个像素;

    ​ iOS用点作为屏幕的坐标测算体系就是为了在Retina设备和普通设备上能有一致的视觉效果

  2. 像素——物理像素坐标并不会用来屏幕布局,但是仍然与图片有相对关系。UIImage是一个屏幕分辨率解决方案,所以指定点来度量大小。但是一些底层的图片表示如CGImage就会使用像素,所以你要清楚在Retina设备和普通设备上,他们表现出来了不同的大小。

  3. 单位—— 对于与图片大小或是图层边界相关的显示,单位坐标是一个方便的度量方式, 当大小改变的时候,也不需要再次调整。单位坐标在OpenGL这种纹理坐标系统中用得很多,Core Animation中也用到了单位坐标。

应用:类似于精灵图技术的图片拼合

1
layer.contentsRect = CGRectMake(0, 0, 0.5, 0.5)

contentsCenter

contentsCenter其实是一个CGRect,它定义了一个固定的边框和一个在图层上可拉伸的区域(9切片技术)

默认情况下,contentsCenter是{0, 0, 1, 1},这意味着如果大小(由conttensGravity决定)改变了,那么寄宿图将会均匀地拉伸开。但是如果我们增加原点的值并减小尺寸。我们会在图片的周围创造一个边框

这意味着我们可以随意重设尺寸,边框仍然会是连续的。他工作起来的效果和UIImage里的-resizableImageWithCapInsets: 方法效果非常类似,只是它可以运用到任何寄宿图,甚至包括在Core Graphics运行时绘制的图形

图展示了`contentsCenter`设置为{0.25, 0.25, 0.5, 0.5}的效果:

 用Interface Builder 探测窗口控制`contentsCenter`属性

利用Core Graphics设置寄宿图

  • contents赋CGImage的值不是唯一的设置寄宿图的方法。我们也可以直接用Core Graphics直接绘制寄宿图
  • 能够通过继承UIView并实现-drawRect:方法来自定义绘制
  • -drawRect: 方法没有默认的实现,因为对UIView来说,寄宿图并不是必须的,它不在意那到底是单调的颜色还是有一个图片的实例
  • 如果UIView检测到-drawRect: 方法被调用了,它就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以 contentsScale的值
  • 如果你不需要寄宿图,那就不要创建这个方法了,这会造成CPU资源和内存的浪费,这也是为什么苹果建议:如果没有自定义绘制的任务就不要在子类中写一个空的-drawRect:方法
  • 当视图在屏幕上出现的时候 -drawRect:方法就会被自动调用
  • -drawRect:方法里面的代码 利用Core Graphics去绘制一个寄宿图,然后内容就会被缓存起来直到它需要被更新(通常是因为开发者调用了-setNeedsDisplay方法
  • -drawRect:方法是一个UIView方法,事实上都是底层的CALayer安排了重绘工作和保存了因此产生的图片

CALayer的delegate属性

  • delegate属性:实现了CALayerDelegate协议,当CALayer需要一个内容特定的信息时,就会从协议中请求

  • CALayerDelegate是一个非正式协议,其实就是说没有CALayerDelegate @protocol可以让你在类里面引用啦。你只需要调用你想调用的方法,CALayer会帮你做剩下的

  • delegate属性被声明为id类型,所有的代理方法都是可选的

  1. 当需要被重绘时,CALayer会请求它的代理给他一个寄宿图来显示

    1
    2
    (void)displayLayer:(CALayerCALayer *)layer;
    // 趁着这个机会,如果代理想直接设置contents属性的话,它就可以这么做,不然没有别的方法可以调用了
  2. 如果代理不实现-displayLayer:方法,CALayer就会转而尝试调用下面这个方法:

1
2
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
//在调用这个方法之前,CALayer创建了一个合适尺寸的空寄宿图(尺寸由bounds和contentsScale决定)和一个Core Graphics的绘制上下文环境,为绘制寄宿图做准备,他作为ctx参数传入
  1. 使用样例:实现CALayerDelegate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];

//create sublayer
CALayer *blueLayer = [CALayer layer];
blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
blueLayer.backgroundColor = [UIColor blueColor].CGColor;

//set controller as layer delegate
blueLayer.delegate = self;

//ensure that layer backing image uses correct scale
blueLayer.contentsScale = [UIScreen mainScreen].scale; //add layer to our view
[self.layerView.layer addSublayer:blueLayer];

//force layer to redraw
[blueLayer display];
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
//draw a thick red circle Core Graphics来绘制图层
CGContextSetLineWidth(ctx, 10.0f);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextStrokeEllipseInRect(ctx, layer.bounds);
}
@end
  • 我们在blueLayer上显式地调用了-display。不同于UIView,当图层显示在屏幕上时,CALayer不会自动重绘它的内容。它把重绘的决定权交给了开发者。
  • 尽管我们没有用masksToBounds属性,绘制的那个圆仍然沿边界被裁剪了。这是因为当你使用CALayerDelegate绘制寄宿图的时候,并没有对超出边界外的内容提供绘制支持。
  • 理解了CALayerDelegate,并知道怎么使用它。但是除非你创建了一个单独的图层,你几乎没有机会用到CALayerDelegate协议

当使用寄宿了视图的图层的时候,你也不必实现-displayLayer:-drawLayer:inContext:方法来绘制你的寄宿图。通常做法是实现UIView的-drawRect:方法,UIView就会帮你做完剩下的工作,包括在需要重绘的时候调用-display方法