圖層與視圖
iOS 視圖在層級關(guān)系中相互嵌套,一個視圖可以管理它的所有子視圖的位置。

在iOS當(dāng)中,所有的視圖都從一個叫做UIVIew的基類派生而來,UIView可以處理觸摸事件,可以支持基于Core Graphics繪圖,可以做仿射變換(例如旋轉(zhuǎn)或者縮放),或者簡單的類似于滑動或者漸變的動畫。
CALayer
CALayer類在概念上和UIView類似,同樣也是一些被層級關(guān)系樹管理的矩形塊,同樣也可以包含一些內(nèi)容(像圖片,文本或者背景色),管理子圖層的位置。它們有一些方法和屬性用來做動畫和變換。和UIView最大的不同是CALayer不處理用戶的交互。CALayer并不清楚具體的響應(yīng)鏈(iOS通過視圖層級關(guān)系用來傳送觸摸事件的機制),于是它并不能夠響應(yīng)事件。
平行的層級關(guān)系
每個UIVIew都有一個CALayer實例的圖層屬性,視圖的職責(zé)就是創(chuàng)建并管理這個圖層,以確保當(dāng)子視圖在層級關(guān)系中添加或者被移除的時候,他們關(guān)聯(lián)的圖層也同樣對應(yīng)在層級關(guān)系樹當(dāng)中有相同的操作。

實際上這些背后關(guān)聯(lián)的圖層才是真正用來在屏幕上顯示和做動畫,UIView僅僅是對它的一個封裝,提供了一些iOS類似于處理觸摸的具體功能,以及Core Animation底層方法的高級接口。
UIView和CALayer提供兩個平行的層級關(guān)系,要做職責(zé)分離,也能避免很多重復(fù)代碼。
實際上,這里并不是兩個層級關(guān)系,而是四個,每一個都扮演不同的角色,除了視圖層級和圖層樹之外,還存在呈現(xiàn)樹和渲染樹。
圖層的能力
雖然蘋果已經(jīng)通過UIView的高級API進行動畫的管理并且使得動畫變得簡單易用。CALayer功能更為靈活:
- 陰影,圓角,帶顏色的邊框
- 3D變換
- 非矩形范圍
- 透明遮罩
- 多級非線性動畫
CALayer 屬性
contents屬性
CALayer 有一個屬性叫做contents,這個屬性的類型被定義為id。如果你給contents賦的不是CGImage,那么你得到的圖層將是空白的。
contents這個奇怪的表現(xiàn)是由Mac OS的歷史原因造成的。它之所以被定義為id類型,是因為在Mac OS系統(tǒng)上,這個屬性對CGImage和NSImage類型的值都起作用。如果你試圖在iOS平臺上將UIImage的值賦給它,只能得到一個空白的圖層。一些初識Core Animation的iOS開發(fā)者可能會對這個感到困惑。
你真正要賦值的類型應(yīng)該是CGImageRef,它是一個指向CGImage結(jié)構(gòu)的指針。UIImage有一個CGImage屬性,它返回一個"CGImageRef",如果你想把這個值直接賦值給CALayer的contents
,那你將會得到一個編譯錯誤。因為CGImageRef并不是一個真正的Cocoa對象,而是一個Core Foundation類型。
盡管Core Foundation類型跟Cocoa對象在運行時貌似很像(被稱作toll-free bridging),他們并不是類型兼容的,不過你可以通過bridged關(guān)鍵字轉(zhuǎn)換。如果要給圖層的寄宿圖賦值,你可以按照以下這個方法:
layer.contents = (__bridge id)image.CGImage;
//如果你沒有使用ARC(自動引用計數(shù)),你就不需要__bridge這部分。
contentGravity
contentsGravity與UIView中的contentsMode類似,UIVIewImage中可以通過設(shè)置contentMode 屬性,使得添加的圖片更適合。
view.contentMode = UIViewContentModeScaleAspectFit;
UIView大多數(shù)視覺相關(guān)的屬性比如contentMode
,對這些屬性的操作其實是對對應(yīng)圖層的操作。
CALayer與contentMode對應(yīng)的屬性叫做contentsGravity,但是它是一個NSString類型,而不是像對應(yīng)的UIKit部分,那里面的值是枚舉。contentsGravity可選的常量值有以下一些:
- kCAGravityCenter
- kCAGravityTop
- kCAGravityBottom
- kCAGravityLeft
- kCAGravityRight
- kCAGravityTopLeft
- kCAGravityTopRight
- kCAGravityBottomLeft
- kCAGravityBottomRight
- kCAGravityResize
- kCAGravityResizeAspect
- kCAGravityResizeAspectFill
和cotentMode一樣,contentsGravity的目的是為了決定內(nèi)容在圖層的邊界中怎么對齊,我們將使用kCAGravityResizeAspect,它的效果等同于UIViewContentModeScaleAspectFit, 同時它還能在圖層中等比例拉伸以適應(yīng)圖層的邊界。
self.layerView.layer.contentsGravity = kCAGravityResizeAspect;
contentsScale
contentsScale屬性定義了寄宿圖的像素尺寸和視圖大小的比例,默認情況下它是一個值為1.0的浮點數(shù)。
如果你只是單純地想放大圖層的contents
圖片,你可以通過使用圖層的transform
和affineTransform屬性來達到這個目的。
contentsScale屬性其實屬于支持高分辨率(又稱Hi-DPI或Retina)屏幕機制的一部分。它用來判斷在繪制圖層的時候應(yīng)該為寄宿圖創(chuàng)建的空間大小,和需要顯示的圖片的拉伸度(假設(shè)并沒有設(shè)置contentsGravity屬性)。
如果contentsScale 設(shè)置為1.0,將會以每個點1個像素繪制圖片,如果設(shè)置為2.0,則會以每個點2個像素繪制圖片,這就是我們熟知的Retina屏幕。
這并不會對我們在使用kCAGravityResizeAspect時產(chǎn)生任何影響,因為它就是拉伸圖片以適應(yīng)圖層而已,根本不會考慮到分辨率問題。但是如果我們把contentsGravity設(shè)置為kCAGravityCenter(這個值并不會拉伸圖片),那將會有很明顯的變化
注意:
當(dāng)我們對Layer添加圖片時設(shè)置contentsGravity為kCAGravityResizeAspect,此時設(shè)置contentsScale不會對圖片顯示有任何影響。但是當(dāng)我們把contentsGravity設(shè)置成kCAGravityCenter,這時就需要通過手動設(shè)置contentsScale修復(fù)圖片不正常顯示問題;
和UIImage不同,CGImage沒有拉伸的概念。當(dāng)我們使用UIImage類去讀取我們的雪人圖片的時候,他讀取了高質(zhì)量的Retina版本的圖片。但是當(dāng)我們用CGImage來設(shè)置我們的圖層的內(nèi)容時,拉伸這個因素在轉(zhuǎn)換的時候就丟失了。不過我們可以通過手動設(shè)置contentsScale來修復(fù)這個問題。
self.layerView.layer.contentsGravity = kCAGravityCenter;
self.layerView.layer.contentsScale = image.scale;
maskToBounds
當(dāng)我們在Layer中添加圖片,但圖片超出Layer范圍時,會有顯示不全的問題。但不管是UIView還是Layer都可以對超出范圍的內(nèi)容或子視圖進行繪制。
UIView中用的是clipsToBounds 來確定是否顯示超出的內(nèi)容是否顯示,CALayer中的則是masksToBounds,當(dāng)把其設(shè)置為YES時,即顯示界外內(nèi)容。
