Core Animation 第二章 寄宿图

序章
第一章

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;
}
向UIView的主图层中添加contents.png

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

对比UIImageView.png

contentsGravity


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

被拉伸的皮卡丘.png

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

view.contentMode = UIViewContentModeScaleAspectFit;

而在CALayer中,这个属性叫做contentsGravity,NSString类型的变量,而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;
处理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;
}
修改过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;
}
maskToBounds设置为YES的皮卡丘.png

contentsRect

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

一个自定义的 contentsRect (左)和之前显示的内容(右).png

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

StoryBoard中的控制器.png

这是我们要使用的图片:

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);   
        }
    }
}

效果如下:

contentsRect效果展示.png

contentsCenter


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

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);
}
实现CALayerDelegate来绘制图层.png

需要注意的是:

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

总结


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

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容