2. The Backing Image
A picture is worth a thousand words. An interface is worth a thousand pictures.
——Ben Shneiderman
CALayer 有一個 id 類型的屬性 contents,但是實際上,只有在你傳入一個 CGImage 時才能起作用。其 id 類型的原因在于當它在 Mac OS 上被使用時,你既可以賦予這個屬性一個 CGImage 或者 NSImage ,但是在 iOS 上,你只能賦一個 CGImage 給它,像 UIImage 這樣的也不行。
實際使用 CALayer 的 contents 屬性時,你需要提供的是一個 CGImageRef,也就是一個指向 CGImage 結(jié)構(gòu)體的指針。UIImage 有一個叫做 CGImage 的屬性,這個屬性會返回一個隱含的 CGImageRef,如果你直接把這個值賦給 CALayer 的 contents 屬性,編譯時是會報錯的,因為 CGImageRef 不是一個 Cocoa 對象,它是 Core Foundation 類型的數(shù)據(jù)。所以,這里我們需要用橋接(bridge cast)的方式來將其轉(zhuǎn)換成 id 類型的數(shù)據(jù):
layer.contents = (__bridge id)image.CGImage;
盡管我們可以通過設(shè)置 CALyer 的 contents 屬性來展示圖片,但是它并不是像 UIImageView 那樣專門用來展示圖片的。
contentsGravity 屬性:類似于 UIView 的contentMode 屬性,決定內(nèi)容的展示位置和尺寸比例,它是一個 NSString 類型的值,我們可以從系統(tǒng)的 framework 中定義的字符串常量選用自己想要的值。
contentScale 屬性:這個屬性定義了 layer 中圖片(contents)的像素尺寸與 view 的尺寸的比例,默認值為 1.0,也就是說在屏幕繪制該圖形時,是按照一點(point)剛好就是1個像素(pixel)的分辨率來處理的,如果這個值為 2.0,那就代表一點中顯示兩個像素的內(nèi)容(Retina 顯示屏就是這樣的分辨率)。值得注意的是,當你將 CALayer 的 contentGravity 屬性設(shè)為 KCAGravityResizeAspect 時,contentScale 屬性是不起作用的。UIView 也有一個類似的屬性 contentScaleFactor,但是我們很少用到。最后,在操作 CALayer 的 contents 時,一定要記得手動設(shè)置 layer 的 contentsScale 屬性來適配屏幕分辨率:
layer.contentScale = [UIScreen mainScreen].scale;
maskToBounds 屬性:類似于 UIView 的 clipsToBounds 屬性,用來裁剪超出 frame 邊界的內(nèi)容。
contentsRect 屬性:用來在 layer 中展示圖片的一部分內(nèi)容。不像 bounds 和 frame 這些以點(point)為單位,contentsRect 以單元坐標(unit coordinates)為單位,取值范圍從 0 到 1,是一個相對值。contentsRect 屬性默認值為{0, 0, 1, 1},也就是說圖片正好完整地顯示在 layer 的 frame 中。
Image Sprites :對 contentsRect 屬性最有意思的應(yīng)用就是能夠使用 image sprites 了。Sprites 一般用在像 Cocos2D 這樣的 2D 游戲引擎中,通過 OpenGL 來顯示圖片。作者 Nick Lockwood 還寫了一個關(guān)于 Sprites 的開源庫:https://github.com/nicklockwood/LayerSprites。
contentCenter 屬性:類似于 UIImage 的 -resizableImageWithCapInsets:方法一樣,確定一塊可伸縮的中間區(qū)域,在 layer 大小發(fā)生改變時,中間區(qū)域(contentCenter)自動伸縮,而四周不變。默認值是{0, 0, 1, 1}。
UIView 默認沒有實現(xiàn) -drawRect:方法,因為如果 UIView 只是填充了某種顏色或者它的 layer 的 contents 已經(jīng)包含了一個 image 實例,這樣的話 UIView 就不需要一個自定義的 backing image了;當 UIView 檢測到 -drawRect:方法被實現(xiàn)了,系統(tǒng)將會為這個 view 生成一個 backing image,這個 backing image 的像素尺寸等于 view 的尺寸乘以 contentScale 。如果你不需要這個 backing image,你就最好不要實現(xiàn) -drawRect:方法,因為這樣會浪費內(nèi)存和 CPU,這也是 Apple 為什么告訴你如果不想進行自定義繪制的話就不要留一個空的-drawRect:方法在那里的原因。
當 view 第一次出現(xiàn)在屏幕上時,-drawRect 方法就被自動調(diào)用了,-drawRect:方法中的自定義實現(xiàn)會被緩存起來,直到 view 需要更新的時候。
盡管 -drawRect:方法是 UIView 的方法,但它實際上是由隱藏在 view 背后的 layer 來管理繪制和存儲圖片的。CALayer 有一個遵循 CALayerDelegate 協(xié)議的 delegate 屬性,當 CALayer 需要繪制信息時,它就會詢問它的 delegate。CALayer 在繪制時會調(diào)用其 delegate 的兩個方法:
- (void)displayLayer:(CALayerCALayer *)layer;
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
除非你單獨用到了 CALyer 來繪制,大多數(shù)情況下,你根本不要實現(xiàn) CALayerDelegate 協(xié)議。因為當 UIView 創(chuàng)建了他的附屬 layer 時,會自動將把它自己設(shè)為那個 layer 的 delegate,并且實現(xiàn)了 -displayLayer:方法。