
contents屬性
contents是CALayer的一個(gè)屬性,類型為id,但如果你用CGImage以外的對象對其賦值的話你只能得到一個(gè)空的圖層。
contents之所以是id類型書中的解釋為:在macOS中,CGImage和NSImage對象都可以對contents屬性起作用。
還需要注意的就是UIImage的 CGImage 屬性實(shí)際是一個(gè) CGImageRef的結(jié)構(gòu)體,所以你需要使用__bridge來進(jìn)行橋接。
layer.contents = (__bridge id)image.CGImage;
書中提到如果是MRC環(huán)境下,則不需要__bridge,不過現(xiàn)在MRC已經(jīng)成為歷史了。
接下來你可以新建一個(gè)工程或者繼續(xù)使用上一章使用的工程,在根控制器的View中添加一個(gè)空白的UIView,作為我們要使用的layerView,設(shè)置contents屬性。
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *image = [UIImage imageNamed:@"pica"];
self.layerView.layer.contents = (__bridge id)image.CGImage;
}

這樣我們就可以不使用UIImageView來顯示圖片了。順便一提,如果你使用了UIImageView來顯示圖片的話,你會發(fā)現(xiàn)UIImageView.layer的contents與你賦值的UIImage的CGImage其實(shí)是同一個(gè)對象。

contentsGravity
再說這個(gè)屬性之前,我們先來對我們的皮卡丘做一點(diǎn)小改動,把200x200的layerView變成320x180,再來看一下,會發(fā)現(xiàn)我們的皮卡丘被拉伸了。

熟悉UIKit的我們很快就能想到修改contentMode來處理圖片的拉伸狀態(tài)。
view.contentMode = UIViewContentModeScaleAspectFit;
而在CALayer中,這個(gè)屬性叫做contentsGravity,NSString類型的變量,而contentMode則是一個(gè)則是一個(gè)對象,這里也可以看出UIView對CALayer進(jìn)行了封裝。contentsGravity可用的NSString常量如下:
- kCAGravityCenter
- kCAGravityTop
- kCAGravityBottom
- kCAGravityLeft
- kCAGravityRight
- kCAGravityTopLeft
- kCAGravityTopRight
- kCAGravityBottomLeft
- kCAGravityBottomRight
- kCAGravityResize
- kCAGravityResizeAspect
- kCAGravityResizeAspectFill
與contentMode一樣,contentsGravity也是處理內(nèi)容在圖層邊界的對齊方式,下面我們使用 kCAGravityResizeAspect來處理我們的layerView。
self.layerView.layer.contentsGravity = kCAGravityResizeAspect;

contentsScale
contentsScale決定了寄宿圖的像素尺寸和視圖大小的比例,默認(rèn)值為1.0。關(guān)于試圖大小,如果你接著使用上面的例子來設(shè)置contentsScale的話,你會發(fā)現(xiàn)contentsScale并沒有生效,因?yàn)槲覀円呀?jīng)設(shè)置了layer的邊界狀態(tài)。而且如果你只是想要放大圖片的話,我們后面還會說到一個(gè)更方便的屬性transform。
更多的時(shí)候我們真正使用到contentsScale屬性是為了適應(yīng)Retain屏幕。它用來判斷繪制圖層時(shí)應(yīng)該為寄宿圖創(chuàng)建的空間大小和需要顯示的拉伸程度(如果沒有設(shè)置contentsGravity的話),與UIView的contentScaleFactor屬性類似。如果contentsScale為1.0則每個(gè)點(diǎn)分配一個(gè)像素,為2.0則每個(gè)點(diǎn)分配兩個(gè)像素用來繪制圖片。由于contentsGravity默認(rèn)值為resize,所以我們需要調(diào)整一下contentsGravity以方便我們看到實(shí)際效果。
- (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;
}

可以看到我們的皮卡丘不但被放大了,而且出現(xiàn)了一些像素化的情況,所以通常使用的時(shí)候應(yīng)該與屏幕的scale對應(yīng)一致。
self.layerView.layer.contentsScale = [UIScreen mainScreen].scale;
maskToBounds
這個(gè)屬性與UIView 的 clipsToBounds 類似,用來決定是否顯示超出邊界的內(nèi)容,設(shè)置為YES則不會顯示超出部分的內(nèi)容。我們可以使用剛剛的例子來測試一下。
- (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;
}

contentsRect
contentsRect屬性允許我們只顯示寄宿圖的某一個(gè)區(qū)域。和bounds,frame不同的是contentsRect的單位不是點(diǎn),而是 0 到 1的一個(gè)單位。默認(rèn)的contentsRect是{0,0,1,1},也就是說整張寄宿圖都是可見的,如果我們制定一個(gè)小一點(diǎn)的矩形,那么圖片就會被剪裁,這里我直接使用書中的圖片來解釋。

下面我們來做一個(gè)簡單的例子,這是我們的VC:

這是我們要使用的圖片:

@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;
//計(jì)算,設(shè)置contentsRect
layerView.layer.contentsRect = CGRectMake(i * spaceX, j * spaceY, spaceX, spaceY);
}
}
}
效果如下:

contentsCenter
首先,contentsCenter與位置無關(guān),他是一個(gè)CGRect類型的屬性,定一個(gè)一個(gè)固定的邊框和在圖層上可以拉伸的范圍。效果與UIImage的拉伸方法類似。例如我們將contentsCenter設(shè)置為{0.25, 0.25, 0.5, 0.5}則圖片的拉伸狀態(tài)如下:

Custom Drawing
給contents賦值CGImage并不是設(shè)置寄宿圖的唯一途徑,我們也可以使用Core Graphics直接繪制寄宿圖。我們可以通過繼承UIView并實(shí)現(xiàn)-drawRect:方法來自定義繪制。當(dāng)UIView檢測到-drawRect:方法被調(diào)用了,就會為視圖分配一個(gè)寄宿圖,這個(gè)寄宿圖的像素尺寸為視圖大小乘以contentsScale。也就是說如果你不需要寄宿圖,那么你最好不要實(shí)現(xiàn)這個(gè)方法,那樣的話會造成CPU資源和內(nèi)存的浪費(fèi)。
當(dāng)視圖出現(xiàn)在屏幕上的時(shí)候
-drawRect:方法就會被調(diào)用,并且繪制的結(jié)果會被緩存起來,當(dāng)開發(fā)者調(diào)用-setNeedsDisplay或者影響視圖表現(xiàn)的屬性,如bounds時(shí),寄宿圖就會被更新。
-drawRect:是一個(gè)UIView的方法,實(shí)際是layer完成了繪制與緩存。當(dāng)寄宿圖需要被重繪的時(shí)候CALayer就會請求它的代理給它一個(gè)寄宿圖來顯示。
- (void)displayLayer:(CALayer *)layer;
如果這個(gè)方法沒有被實(shí)現(xiàn),CALayer就會嘗試下面的方法:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
接下來我們來實(shí)際繪制一個(gè)寄宿圖吧。
- (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);
}

需要注意的是:
- 我們對blueLayer調(diào)用了display方法,這是因?yàn)镃ALayer不會因?yàn)楸伙@示到屏幕上就自動重繪。
- 我們并沒有設(shè)置maskToBounds屬性,但是視圖依然被剪裁了,這是因?yàn)槭褂肅ALayerDelegate繪制寄宿圖的時(shí)候,并沒有對邊界外繪制提供支持。
總結(jié)
本章主要講述了 contents,寄宿圖相關(guān)的屬性,以及如何使用CALayerDelegate繪制寄宿圖。