Core Animation 第二章 寄宿图

序章
第一章

Core Animation 第二章 寄宿图_第1张图片
Paste_Image.png

contents属性


contentsCALayer的一个属性,类型为id,但如果你用CGImage以外的对象对其赋值的话你只能得到一个空的图层。

contents之所以是id类型书中的解释为:在macOS中,CGImageNSImage对象都可以对contents属性起作用。

还需要注意的就是UIImageCGImage 属性实际是一个 CGImageRef的结构体,所以你需要使用__bridge来进行桥接。

layer.contents = (__bridge id)image.CGImage;

书中提到如果是MRC环境下,则不需要__bridge,不过现在MRC已经成为历史了。

接下来你可以新建一个工程或者继续使用上一章使用的工程,在根控制器的View中添加一个空白的UIView,作为我们要使用的layerView,设置contents属性。

- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *image = [UIImage imageNamed:@"pica"];
    self.layerView.layer.contents = (__bridge id)image.CGImage;
}
Core Animation 第二章 寄宿图_第2张图片
向UIView的主图层中添加contents.png

这样我们就可以不使用UIImageView来显示图片了。顺便一提,如果你使用了UIImageView来显示图片的话,你会发现UIImageView.layercontents与你赋值的UIImageCGImage其实是同一个对象。

Core Animation 第二章 寄宿图_第3张图片
对比UIImageView.png

contentsGravity


再说这个属性之前,我们先来对我们的皮卡丘做一点小改动,把200x200的layerView变成320x180,再来看一下,会发现我们的皮卡丘被拉伸了。

Core Animation 第二章 寄宿图_第4张图片
被拉伸的皮卡丘.png

熟悉UIKit的我们很快就能想到修改contentMode来处理图片的拉伸状态。

view.contentMode = UIViewContentModeScaleAspectFit;

而在CALayer中,这个属性叫做contentsGravityNSString类型的变量,而contentMode则是一个则是一个对象,这里也可以看出UIViewCALayer进行了封装。contentsGravity可用的NSString常量如下:

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

与contentMode一样,contentsGravity也是处理内容在图层边界的对齐方式,下面我们使用 kCAGravityResizeAspect来处理我们的layerView。

self.layerView.layer.contentsGravity = kCAGravityResizeAspect;
Core Animation 第二章 寄宿图_第5张图片
处理contentsGravity后的皮卡丘.png

contentsScale


contentsScale决定了寄宿图的像素尺寸和视图大小的比例,默认值为1.0。关于试图大小,如果你接着使用上面的例子来设置contentsScale的话,你会发现contentsScale并没有生效,因为我们已经设置了layer的边界状态。而且如果你只是想要放大图片的话,我们后面还会说到一个更方便的属性transform
更多的时候我们真正使用到contentsScale属性是为了适应Retain屏幕。它用来判断绘制图层时应该为寄宿图创建的空间大小和需要显示的拉伸程度(如果没有设置contentsGravity的话),与UIViewcontentScaleFactor属性类似。如果contentsScale为1.0则每个点分配一个像素,为2.0则每个点分配两个像素用来绘制图片。由于contentsGravity默认值为resize,所以我们需要调整一下contentsGravity以方便我们看到实际效果。

- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *image = [UIImage imageNamed:@"pica"];
    self.layerView.layer.contents = (__bridge id)image.CGImage;
    self.layerView.layer.contentsGravity = kCAGravityCenter;
    self.layerView.layer.contentsScale = image.scale;
}
Core Animation 第二章 寄宿图_第6张图片
修改过contentsScale的皮卡丘.png

可以看到我们的皮卡丘不但被放大了,而且出现了一些像素化的情况,所以通常使用的时候应该与屏幕的scale对应一致。

self.layerView.layer.contentsScale = [UIScreen mainScreen].scale;

maskToBounds


这个属性与UIView 的 clipsToBounds 类似,用来决定是否显示超出边界的内容,设置为YES则不会显示超出部分的内容。我们可以使用刚刚的例子来测试一下。

- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *image = [UIImage imageNamed:@"pica"];
    self.layerView.layer.contents = (__bridge id)image.CGImage;
    self.layerView.layer.contentsGravity = kCAGravityCenter;
//    self.layerView.layer.contentsScale = [UIScreen mainScreen].scale;
    self.layerView.layer.contentsScale = image.scale;
    self.layerView.layer.masksToBounds = YES;
}
Core Animation 第二章 寄宿图_第7张图片
maskToBounds设置为YES的皮卡丘.png

contentsRect

contentsRect属性允许我们只显示寄宿图的某一个区域。和boundsframe不同的是contentsRect的单位不是点,而是 0 到 1的一个单位。默认的contentsRect是{0,0,1,1},也就是说整张寄宿图都是可见的,如果我们制定一个小一点的矩形,那么图片就会被剪裁,这里我直接使用书中的图片来解释。

Core Animation 第二章 寄宿图_第8张图片
一个自定义的 contentsRect (左)和之前显示的内容(右).png

下面我们来做一个简单的例子,这是我们的VC:

Core Animation 第二章 寄宿图_第9张图片
StoryBoard中的控制器.png

这是我们要使用的图片:

Core Animation 第二章 寄宿图_第10张图片
dribbble.png
@interface ContentsRectController ()
@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *contentViews;
@end

@implementation ContentsRectController

- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *image = [UIImage imageNamed:@"dribbble-1"];
    CGFloat spaceX = 1.0 / 3.0;
    CGFloat spaceY = 1.0 / 2.0;
    for (int i = 0; i < 3; i ++) {
        for (int j = 0; j < 2; j ++) {
            //获取collection中的view
            UIView *layerView = self.contentViews[i * 2 + j];
            layerView.layer.contents = (__bridge id)image.CGImage;
            //计算,设置contentsRect
            layerView.layer.contentsRect = CGRectMake(i * spaceX, j * spaceY, spaceX, spaceY);   
        }
    }
}

效果如下:

Core Animation 第二章 寄宿图_第11张图片
contentsRect效果展示.png

contentsCenter


首先,contentsCenter与位置无关,他是一个CGRect类型的属性,定一个一个固定的边框和在图层上可以拉伸的范围。效果与UIImage的拉伸方法类似。例如我们将contentsCenter设置为{0.25, 0.25, 0.5, 0.5}则图片的拉伸状态如下:

Core Animation 第二章 寄宿图_第12张图片
contentsCenter.png

Custom Drawing


contents赋值CGImage并不是设置寄宿图的唯一途径,我们也可以使用Core Graphics直接绘制寄宿图。我们可以通过继承UIView并实现-drawRect:方法来自定义绘制。当UIView检测到-drawRect:方法被调用了,就会为视图分配一个寄宿图,这个寄宿图的像素尺寸为视图大小乘以contentsScale。也就是说如果你不需要寄宿图,那么你最好不要实现这个方法,那样的话会造成CPU资源和内存的浪费。

当视图出现在屏幕上的时候-drawRect:方法就会被调用,并且绘制的结果会被缓存起来,当开发者调用-setNeedsDisplay或者影响视图表现的属性,如bounds时,寄宿图就会被更新。

-drawRect:是一个UIView的方法,实际是layer完成了绘制与缓存。当寄宿图需要被重绘的时候CALayer就会请求它的代理给它一个寄宿图来显示。

- (void)displayLayer:(CALayer *)layer;

如果这个方法没有被实现,CALayer就会尝试下面的方法:

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;

接下来我们来实际绘制一个寄宿图吧。

- (void)viewDidLoad {
    [super viewDidLoad];   
    CALayer *blueLayer = [CALayer layer];
    blueLayer.frame = CGRectMake(50.f, 50.f, 100.f, 100.f);
    blueLayer.backgroundColor = [UIColor blueColor].CGColor;
    blueLayer.delegate = self;
    blueLayer.contentsScale = [UIScreen mainScreen].scale;
    [self.layerView.layer addSublayer:blueLayer];
    [blueLayer display];
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    CGContextSetLineWidth(ctx, 10.0f);
    CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
    CGContextStrokeEllipseInRect(ctx, layer.bounds);
}
Core Animation 第二章 寄宿图_第13张图片
实现CALayerDelegate来绘制图层.png

需要注意的是:

  • 我们对blueLayer调用了display方法,这是因为CALayer不会因为被显示到屏幕上就自动重绘。
  • 我们并没有设置maskToBounds属性,但是视图依然被剪裁了,这是因为使用CALayerDelegate绘制寄宿图的时候,并没有对边界外绘制提供支持。

总结


本章主要讲述了 contents,寄宿图相关的属性,以及如何使用CALayerDelegate绘制寄宿图。

你可能感兴趣的:(Core Animation 第二章 寄宿图)