一圖勝千言周瞎,一接口勝千圖渊迁。——Ben Shneiderman
第1章“圖層樹”介紹了CALayer
類并創(chuàng)建了一個(gè)簡(jiǎn)單有藍(lán)色背景的圖層剃允。背景色非常棒淆九,但如果圖層只能顯示一個(gè)顏色將會(huì)顯得平庸無常。CALayer
可以容納任何你喜歡的圖片做鹰。這一章將講述CALayer
的主圖像谋作。
內(nèi)容圖像
CALayer
有一個(gè)叫contents
的屬性寂殉,這個(gè)屬性被定義為id
類型[1],意味它可以作為任意類的對(duì)象斑举。這是正確的铲敛,這意味著你可以給contents
屬性定義為任何你喜歡的對(duì)象,而你的應(yīng)用程序仍可以編譯成功——然而儿惫,實(shí)際上如果你提供了一個(gè)不是CGImage
的對(duì)象饵逐,你的圖層將變?yōu)橐黄瞻住?/p>
contents
屬性的這一奇怪特性是由于Core Animation
是繼承于Mac OS的奸柬。在Mac OS上contents
被定義為id
的原因是你可以給這一屬性賦值為CGImage
或者NSImage
而它會(huì)自動(dòng)生效铃肯。如果你嘗試在iOS上賦值一個(gè)UIImage
雾消,你就只會(huì)得到一個(gè)空白的圖層。這對(duì)于新接觸Core Animation
的iOS開發(fā)者來說是一個(gè)普遍的困惑之處采呐。
頭疼之處不僅如此洋只。事實(shí)上妻献,你真正所需要提供的類型是CGImageRef
析蝴,這是一個(gè)指向CGImage
結(jié)構(gòu)體的指針赴精。UIImage
有一個(gè)CGImage
屬性會(huì)返回CGImageRef
琼富。如果你嘗試直接賦值CALayer content
屬性郊艘,會(huì)無法通過編譯瞎嬉。這是因?yàn)?code>CGImageRef并不是一個(gè)真正的Cocoa
對(duì)象,它是一個(gè)Core Foundation類型
。
盡管Core Foundation
類型運(yùn)行時(shí)表現(xiàn)得如同是Cocoa
對(duì)象(被稱為toll-free bridging)喘先,除非你使用一個(gè)bridged轉(zhuǎn)換壁酬,否則它們并不等同于id
。[2]賦值一個(gè)圖層的圖像只需要這樣做:
layer.contents = (__bridge id)image.CGImage;
如果你不使用ARC(自動(dòng)引用計(jì)數(shù))因块,你不需要加上bridge
雁佳,但是你為什么不用ARC?腿堤!
讓我們修改在第1章中創(chuàng)建的項(xiàng)目來顯示一張圖像而非一個(gè)背景顏色。既然我們可以程序化地創(chuàng)建圖層而我們不再需要額外的圖層,所以我們直接將layerView
中的contents
屬性直接設(shè)置為圖像[3]涣达。
表2.1顯示了升級(jí)后的代碼,圖2.1顯示了結(jié)果扎拣。
表2.1 設(shè)置CGImage作為圖層contents
override func viewDidLoad() {
super.viewDidLoad()
// 加載圖像
let image = UIImage(named: "Snowman.png")!
// 直接添加到我們的視圖圖層中
self.layerView.layer.contents = image.CGImage
}
這是非常簡(jiǎn)單的代碼拳昌,但我們已經(jīng)在這做了一些相當(dāng)有趣的事情:使用CALayer
的在一個(gè)普通的UIView
中顯示一個(gè)圖像。這不是一個(gè)UIImageView
,并不是正常設(shè)計(jì)來顯示圖像的。但通過直接操作圖層溯饵,我們發(fā)現(xiàn)了新的方法并使得我們平庸的UIView
有趣了一點(diǎn)渣玲。
contentsGravity
你可能注意到我們的雪人看起來有一點(diǎn)...胖星掰。我們加載的圖片并不是一個(gè)準(zhǔn)確的正方形果覆,但它被拉伸以適應(yīng)這個(gè)視圖昭卓。你可能在使用UIImageView
中碰見過相似的情形返干,解決方法是給這個(gè)視圖設(shè)置更為合適contentMode
屬性栓袖。就像這樣:
view.contentMode = UIViewContentMode.ScaleAspectFit
這個(gè)方法效果不錯(cuò)(自己試一下)买鸽,但大多UIView
的視覺屬性茅诱,例如contentMode
就是直接操作底層圖層的相應(yīng)屬性矩肩。
CALayer
中的相應(yīng)屬性叫contentsGravity
,而且它是一個(gè)NSString
而不是像UIKit中是一個(gè)enum
。這個(gè)contentsGravity
字符串應(yīng)該被設(shè)為如下幾個(gè)常量:
- kCAGravityCenter
- kCAGravityTop
- kCAGravityBottom
- kCAGravityLeft
- kCAGravityRight
- kCAGravityTopLeft
- kCAGravityTopRight
- kCAGravityBottomLeft
- kCAGravityBottomRight
- kCAGravityResize
- kCAGravityResizeAspect
- kCAGravityResizeAspectFill
正如contentMode
一樣,contentsGravity
的目的在于確定內(nèi)容應(yīng)該怎么和圖層邊界對(duì)齊窟坐。我們將使用kCAGravityResizeAspect
這等同于UIViewContentMode.ScaleAspectFit
儒老,它可以縮放圖像使其適合圖層的邊界而不會(huì)改變它的比例:
self.layerView.layer.contentsGravity = kCAGravityResizeAspect
圖2.2顯示這個(gè)結(jié)果辽俗。
contentsScale
contentsScale
屬性定義了圖層主圖像的像素尺寸和視圖大小的比例。它是一個(gè)默認(rèn)為1.0的浮點(diǎn)數(shù)。
contentsScale
屬性的目的并不是很直接,它并不是總能夠影響到屏幕上的主圖像的縮放辛萍;如果你嘗試在我們的雪人例子中將它設(shè)置為不同的值谆棺,你會(huì)發(fā)現(xiàn)它并沒有任何效果侍郭,因?yàn)?code>contents圖像早已經(jīng)通過contentsGravity
屬性縮放成適合圖層邊界。
如果你想簡(jiǎn)單的縮放圖層的contents
圖像掠河,你可以使用圖層的transform
或是affineTransform
屬性(看第5章“變形”亮元,用于于解釋變形),但這并不是`contentsScale的目的唠摹。
contentsScale
屬性實(shí)際上是用來支持高分辨率(也被稱為Hi-DPI或者Retina)屏幕實(shí)現(xiàn)機(jī)制的一部分爆捞。它被用于確認(rèn)主圖像的大小,當(dāng)繪制時(shí)這個(gè)圖層應(yīng)當(dāng)自動(dòng)創(chuàng)建勾拉,以及contents
圖像應(yīng)該被顯示的縮放尺寸(假設(shè)這還沒有被contentsGravity
設(shè)置縮放)煮甥。UIView
有一個(gè)相同但很少使用的屬性叫作contentScaleFactor
。
如果contentsScale
被設(shè)為1.0藕赞,將以每點(diǎn)1像素的分辨率繪制成肘。如果被設(shè)為2.0,將以沒電2像素的分辨率繪制斧蜕,也被稱作Retina分辨率双霍。(如果你不是很清楚像素和點(diǎn)之間的差異,稍后將解釋批销。)
這與使用kCAGravityResizeAspect
并不會(huì)有任何不同洒闸,因?yàn)闊o論什么分辨率,它將縮放圖像以適應(yīng)圖層均芽。但如果我們將我們的contentsGravity
設(shè)為kCAGravityCenter
(這個(gè)并不會(huì)縮放圖像)丘逸,這之間的差異會(huì)更加明顯(見圖2.3)。
正如你所見掀宋,我們的雪人非常大而且像素化深纲。這是因?yàn)?code>CGImage(不像UIImage
)一樣并沒有縮放的內(nèi)部概念仲锄。當(dāng)我們用UIImage
類來加載我們的雪人圖像,它會(huì)正確地加載為高質(zhì)量的Retina版本囤萤。但當(dāng)我們用CGImage
作為我們的圖層contents
的圖像昼窗,縮放因子在變形中丟失。我們可以通過手動(dòng)的設(shè)置contentsScale
來修復(fù)它涛舍,這樣可以匹配_UIImage scale
屬性(見表2.2)澄惊。圖2.4顯示了結(jié)果。
override func viewDidLoad() {
super.viewDidLoad()
// 加載圖像
let image = UIImage(named: "Snowman.png")!
// 直接添加到我們的視圖圖層中
self.layerView.layer.contents = image.CGImage
// 將圖片放在正中間
self.layerView.layer.contentsGravity = kCAGravityCenter
// 改變contentsScale來適應(yīng)圖像的縮放
self.layerView.layer.contentsScale = image.scale
}
當(dāng)程序化生成主圖像時(shí)富雅,你應(yīng)當(dāng)時(shí)常記得手動(dòng)設(shè)置圖層的contentsScale
來匹配屏幕縮放掸驱;否則,你的圖像將會(huì)在Retina設(shè)備上顯示成像素化没佑。你可以像這樣做:
layer.contentsScale = UIScreen.mainScreen().scale
masksToBounds
既然我們的雪人已經(jīng)按正常的大小顯示毕贼,你可能注意到了其它東西——他沿伸到視圖的邊界之外。默認(rèn)情況下蛤奢,UIView
將會(huì)很開心地繪制它指定邊界之外的內(nèi)容和子視圖鬼癣,而這同樣適用于CALayer
。
在UIView
中有一個(gè)屬性叫做clipsToBounds
用來開關(guān)裁剪(這是用來控制是否一個(gè)視圖的內(nèi)容可以蔓延到它們的幀之外)啤贩。CALayer
有一個(gè)相同的屬性叫做masksToBounds
待秃。當(dāng)設(shè)為真
時(shí),我們可以限定我們的雪人在視圖之內(nèi)(見圖2.5)痹屹。
contentsRect
CALayer
中的contentsRect
屬性允許我們指定一個(gè)子矩形來在圖層幀中顯示主圖像章郁。這比contentsGravity
提供更多的自由性來決定圖像是如何被裁剪或拉伸。
而與bounds
以及frame
不同的是contentsRect
并不是以點(diǎn)度量的志衍;它使用單元坐標(biāo)暖庄。單元坐標(biāo)被限定為0到1之間,是相對(duì)值(不同于點(diǎn)和像素等絕對(duì)值)楼肪。在這個(gè)侄子中培廓,它們相對(duì)于主圖像的尺寸。下面這些坐標(biāo)類型被用于iOS:
- 點(diǎn)——在iOS和Mac OS最廣泛使用的坐標(biāo)類型淹辞。點(diǎn)是虛擬的像素医舆,也被稱作邏輯像素。在標(biāo)準(zhǔn)定義的設(shè)備上象缀,一點(diǎn)相當(dāng)于一個(gè)像素蔬将,但在Retina設(shè)備上,一點(diǎn)相當(dāng)于2*2個(gè)物理像素央星。iOS在所有的屏幕坐標(biāo)度量中使用點(diǎn)來使得布局可以無縫地使用于Retina和非Retina屏的設(shè)備上霞怀。
-
像素——物理像素坐標(biāo)并不用于屏幕布局,但它們通常與圖像處理相關(guān)莉给。
UIImage
是屏幕分辨相關(guān)的毙石,并且以點(diǎn)來限定大小廉沮,但有些底層圖像顯示例如CGImage
使用像素尺寸,因此你應(yīng)當(dāng)記住它們的狀態(tài)尺寸并不會(huì)匹配它們?cè)谝粋€(gè)Retina設(shè)備上的顯示尺寸徐矩。 -
單元——單元坐標(biāo)是一個(gè)用來確切度量相對(duì)的圖像大小或圖層邊界的簡(jiǎn)便方法皱蹦,因此當(dāng)大小改變時(shí)不需要調(diào)整附较。單元坐標(biāo)在OpenGL廣泛用于紋理坐標(biāo)等阐滩,它們也常常用于
Core Animation
中忧饭。
默認(rèn)的contentsRect
是{0, 0, 1, 1}
,這意味著整個(gè)主圖像是默認(rèn)可見的鳞骤,如果我們指定一個(gè)小一點(diǎn)的矩形窒百,這個(gè)圖像會(huì)被裁剪(如圖2.6)。
將contentsRect
指定為一個(gè)負(fù)數(shù)或者大于{1,1}
的尺寸是被允許的豫尽。在這種情況下篙梢,圖像最外的像素會(huì)被拉伸來填充指定區(qū)域。
contentsRect
的一個(gè)有趣之處在于它可以被用于叫做圖像精靈的東西美旧。如果你曾做過任何游戲編程渤滞,你會(huì)對(duì)精靈的概念感到熟悉。這其實(shí)是一種可以獨(dú)立在屏幕到處移動(dòng)的圖像榴嗅。但在游戲世界之外蔼水,這個(gè)術(shù)語通常用于指代一種用于加載精靈圖像的通用技術(shù),而不是使一切事物動(dòng)起來录肯。
通常,許多精靈會(huì)被打包成一張大圖片來一次性加載吊说。這比使用多張獨(dú)立的照片在內(nèi)存使用论咏、加載時(shí)間和渲染表現(xiàn)上等上更多的優(yōu)點(diǎn)。
精靈被用于2D游戲引擎如Cocos2d颁井,在其中用OpenGL來顯示圖像厅贪。但我們可以通過借助contentsRect
的力量在一個(gè)普通的UIKit應(yīng)用程序中使用精靈。
在開始前雅宾,我們需要一個(gè)精靈圖集[4]——一張包含我們小一點(diǎn)的精靈圖像的大圖像养涮。圖2.7展示了一個(gè)精靈圖集的案例。
接下來眉抬,我們要在我們的應(yīng)用中加載并顯示這些精靈贯吓。原則十分簡(jiǎn)單:我們正常加載我們的大圖像,將它賦值給四個(gè)分離圖層的contents
(每一個(gè)對(duì)應(yīng)一個(gè)精靈)蜀变,然后設(shè)置它們每個(gè)的contentsRect
來遮蓋我們不需要的部分悄谐。
我們需要在我們的項(xiàng)目中為我們的精靈圖層增加一些額外的視圖。(這些視圖是用Interface Builder定位的库北,這樣可以防止產(chǎn)生雜亂的代碼爬舰,但如果你喜歡也可以用程序化的方式創(chuàng)建它們们陆。)表2.3展示了代碼,圖2.8展示了最終的結(jié)果情屹。
表2.3 使用contentsRect切割精靈圖集
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var coneView: UIView!
@IBOutlet weak var shipView: UIView!
@IBOutlet weak var iglooView: UIView!
@IBOutlet weak var anchorView: UIView!
func addSpriteImage(image: UIImage, withContentRect rect: CGRect, toLayer layer: CALayer) {
// 設(shè)置圖像
layer.contents = image.CGImage
// 縮放內(nèi)容以適應(yīng)
layer.contentsGravity = kCAGravityResizeAspectFill
// 設(shè)置內(nèi)容矩形
layer.contentsRect = rect
}
override func viewDidLoad() {
super.viewDidLoad()
// 加載精靈圖集
let image = UIImage(named: "Sprites.png")!
// 設(shè)置冰屋
self.addSpriteImage(image, withContentRect: CGRectMake(0, 0, 0.5, 0.5), toLayer: self.iglooView.layer)
// 設(shè)置圓錐
self.addSpriteImage(image, withContentRect: CGRectMake(0.5, 0, 0.5, 0.5), toLayer: self.coneView.layer)
// 設(shè)置錨
self.addSpriteImage(image, withContentRect: CGRectMake(0, 0.5, 0.5, 0.5), toLayer: self.anchorView.layer)
// 設(shè)置飛船
self.addSpriteImage(image, withContentRect: CGRectMake(0.5, 0.5, 0.5, 0.5), toLayer: self.shipView.layer)
}
}
精靈圖集是一個(gè)減少應(yīng)用大小和優(yōu)化加載表現(xiàn)(一張大圖比多張小圖壓縮更好且加載更快)的簡(jiǎn)潔的方式,但人工設(shè)置它們會(huì)比較笨拙垃你,當(dāng)精靈圖集創(chuàng)建完成之后椅文,增加新精靈或修改某個(gè)已有精靈的尺寸一直是一個(gè)大問題。
一些商業(yè)應(yīng)用可以在你的Mac上自動(dòng)創(chuàng)建精靈圖集蜡镶。這些工具通過生成XML或Plist文件來存儲(chǔ)精靈坐標(biāo)來簡(jiǎn)化精靈的使用雾袱。這些文件可以隨圖像一起加載并用于給每個(gè)精靈設(shè)置contentsRect
,而非讓開發(fā)者們不得不去手工用代碼在應(yīng)用中布置它們官还。
這些文件通常被設(shè)計(jì)為應(yīng)用于OpenGL的游戲中芹橡,但如果你對(duì)在一個(gè)常規(guī)的應(yīng)用中使用精靈圖集,這個(gè)開源圖層精靈庫可以用流行的Cocos2d
格式讀入精靈圖集并用正常的Core Animation
圖層顯示它們望伦。
contentsCenter
這一章我們講解的最后一個(gè)內(nèi)容相關(guān)的屬性是contentsCenter
林说。你可能根據(jù)名字推測(cè)contentsCenter
這一屬性將會(huì)與contents
圖像的位置有關(guān),但事實(shí)上這個(gè)名字是有誤導(dǎo)性的屯伞。contentsCenter
實(shí)際上是一個(gè)定義圖層可拉伸區(qū)域和一個(gè)邊緣固定邊框的CGRect
腿箩。改變contentsCenter
對(duì)于主圖像如何顯示毫無影響,除非圖層被重新定義大小劣摇,那么它的目的就變得清晰了珠移。默認(rèn)情況下contentsCenter
被設(shè)為{0, 0, 1, 1}
,這意味著當(dāng)圖層被重新定義大小時(shí)(由contentsGravity
決定)主圖像將會(huì)均勻拉伸末融。但如果我們?cè)黾娱_始的值并減少大小钧惧,我們可以在圖像四周創(chuàng)建邊框。圖2.9展示了{0.25, 0.25, 0.5, 0.5}
這組數(shù)的縮放效果的勾习。
這意味著當(dāng)我們直接重新調(diào)整視圖大小的時(shí)候浓瞪,邊框?qū)⒈3忠恢拢ㄈ鐖D2.10)。這與使用UIImage
的-resizableImageWithCapInsets:
方法相似巧婶,但可以應(yīng)用于任一圖層的主圖像乾颁,甚至包換在運(yùn)行時(shí)用Core Graphics
繪制的圖層(正如這章后面將講述的一樣)。[5]
表2.4展示了用于程序化設(shè)置這些可拉伸圖像的代碼艺栈。然而英岭,contentsCenter
另一個(gè)很棒的額外特性是,它可以不用寫任何代碼湿右,而在Interface Builder的檢查器窗口中使用拉伸控制來設(shè)置巴席,正如圖2.11所示。
表2.4 使用contentsCenter設(shè)置可拉伸視圖
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var button1: UIView!
@IBOutlet weak var button2: UIView!
func addStretchableImage(image: UIImage, withContentCenter rect: CGRect, toLayer layer: CALayer) {
// 設(shè)置圖像
layer.contents = image.CGImage
// 設(shè)置內(nèi)容中心
layer.contentsCenter = rect
}
override func viewDidLoad() {
super.viewDidLoad()
// 加載按鈕圖像
let image = UIImage(named: "Button.png")!
// 設(shè)置button1
self.addStretchableImage(image, withContentCenter: CGRectMake(0.25, 0.25, 0.5, 0.5), toLayer: self.button1.layer)
// 設(shè)置button2
self.addStretchableImage(image, withContentCenter: CGRectMake(0.25, 0.25, 0.5, 0.5), toLayer: self.button2.layer)
}
}
自定義繪圖
用一張CGImage
設(shè)置圖層的contents
并不是唯一設(shè)置主圖像的方法诅需。你也可以通過使用Core Graphics
來直接繪制主圖像漾唉。-drawRect:
方法可以在一個(gè)UIView
的子類中實(shí)現(xiàn)自定義繪圖荧库。
-drawRect:
方法沒有默認(rèn)的實(shí)現(xiàn),這是因?yàn)槿绻麅H是用一個(gè)純色填充或著底圖層的contents
屬性包含一個(gè)已存在的圖像實(shí)例赵刑,UIView
并不需要一個(gè)自定義的主圖像分衫。如果UIView
檢測(cè)到-drawRect:
方法出現(xiàn),它將為這個(gè)視圖分配一個(gè)新的主圖像般此,其像素大小等同于視圖大小乘以contentsScale
蚪战。
如果你不需要這個(gè)主圖像,創(chuàng)建它將浪費(fèi)內(nèi)存和CPU铐懊。這就是為什么Apple建議你邀桑,如果不是想自定義一些繪制的時(shí)候,不要在你的圖層子類中存在一個(gè)空的-drawRect:
方法科乎。
-drawRect:
方法會(huì)在視圖第一次出現(xiàn)在屏幕上時(shí)自動(dòng)執(zhí)行壁畸。-drawRect:
方法中的代碼會(huì)使用Core Graphics
來繪制主圖像,其結(jié)果在被緩存直至視圖需要更新(通常因?yàn)殚_發(fā)者調(diào)用了-setNeedsDisplay
方法茅茂,然而某些視圖類型在當(dāng)一個(gè)會(huì)影響它們顯示的屬性被改變時(shí)會(huì)自動(dòng)重繪[如bounds
])捏萍。盡管-drawRect:
是一個(gè)UIView
的方法,它實(shí)際上是在CALayer
下安排繪制并存儲(chǔ)結(jié)果圖像的空闲。
CALayer
有一個(gè)可選的遵守CALayerDelegate
協(xié)議的delegate
屬性令杈。當(dāng)CALayer
需要特定內(nèi)容的信息時(shí),它將向這個(gè)delegate
中請(qǐng)求相關(guān)信息碴倾。CALayerDelegate
是一個(gè)非正式的協(xié)議逗噩,這是說明事實(shí)上并沒有CALayerDelegate @protocol
可以讓你在類接口的引用。你只需要加上你需要的方法跌榔,而CALayer
會(huì)在出現(xiàn)時(shí)調(diào)用它們给赞。(這個(gè)delegate
屬性被聲明為id
并且所有的委托方法被當(dāng)作可選的對(duì)待。)
當(dāng)它需要被重繪時(shí)矫户,CALayer
會(huì)讓它的委托提供一個(gè)主圖像用于顯示。為這實(shí)現(xiàn)這一功能它會(huì)嘗試調(diào)用下面這個(gè)方法:
objc:
- (void)displayLayer: (CALayer nonnull *)layer;
swift:
func displayLayer(_ layer: CALayer)
這個(gè)時(shí)候委托可以直接設(shè)置圖層的contents
残邀,之后不會(huì)再有其它方法被調(diào)用皆辽。如果委托沒有實(shí)現(xiàn)-displayLayer:
方法,CALayer
會(huì)嘗試調(diào)用如上方法來代替:
objc:
- (void)drawLayer: (CALayer * nonnull)layer inContext: (CGContextRef nonnull)ctx
swift:
func drawLayer(_ layer: CALayer, inContext ctx: CGContext)
在調(diào)用這一方法前芥挣,CALayer
會(huì)創(chuàng)建一個(gè)合適大星啤(基于圖層的bounds
以及contentsScale
)的空主圖像和一個(gè)合適的Core Graphics
繪制上下文來繪制這個(gè)圖像,這就是它傳遞的ctx
參數(shù)空免。
讓我們修改第1章的測(cè)試程序來實(shí)現(xiàn)CALayerDelegate
協(xié)議空另,然后繪制一些圖像(如表2.5)。圖2.12展示了結(jié)果蹋砚。
表2.5 實(shí)現(xiàn)CALayerDelegate
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var layerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// 創(chuàng)建子圖層
let blueLayer = CALayer()
blueLayer.frame = CGRectMake(50.0, 50.0, 100.0, 100.0)
blueLayer.backgroundColor = UIColor.blueColor().CGColor
// 設(shè)置當(dāng)前控制器作為圖層代理
blueLayer.delegate = self
// 確保圖層的主圖像使用正確的縮放
blueLayer.contentsScale = UIScreen.mainScreen().scale
// 將圖層加入視圖
self.layerView.layer.addSublayer(blueLayer)
// 強(qiáng)制圖層重新繪制
blueLayer.display()
}
override func drawLayer(layer: CALayer!, inContext ctx: CGContext!) {
// 繪制一個(gè)粗的紅色圓圈
CGContextSetLineWidth(ctx, 10.0)
CGContextSetStrokeColorWithColor(ctx, UIColor.redColor().CGColor)
CGContextStrokeEllipseInRect(ctx, layer.bounds)
}
}
一些有趣的注意事項(xiàng):
- 我們不得不手動(dòng)調(diào)用
blueLayer
的-display
方法來強(qiáng)制其重新繪制扼菠。不同于UIView
摄杂,CALayer
在顯示在屏幕上時(shí)并不會(huì)自動(dòng)重新繪制里面的內(nèi)容;它留給開發(fā)者去謹(jǐn)慎決定是否圖層需要重新繪制循榆。 - 即使我們沒有允許
masksToBounds
屬性析恢,我們繪制的圓也會(huì)被裁剪成圖層的bounds
。這是因?yàn)楫?dāng)你用CALayerDelegate
繪制主圖像時(shí)秧饮,CALayer
會(huì)創(chuàng)建一個(gè)與圖層尺寸一樣大小的上下文映挂。并不存在可以畫出邊界的選擇。
到現(xiàn)在你應(yīng)該理解了CALayerDelegate
并知道如何去使用它盗尸。但除非你在創(chuàng)建獨(dú)立的圖層柑船,你幾乎不會(huì)需要去實(shí)現(xiàn)CALayerDelegate
協(xié)議。其原因在于當(dāng)UIView
創(chuàng)建主圖像時(shí)泼各,它會(huì)自動(dòng)將自己設(shè)為圖層的delegate
并提供-displayLayer:
抽象問題的一種實(shí)現(xiàn)鞍时。
當(dāng)使用基于視圖的圖層時(shí),你并不需要去實(shí)現(xiàn)-displayLayer:
或者-drawLayer:inContext:
來繪制你圖層的主圖像历恐;你可以直接用正常方式實(shí)現(xiàn)UIView
的-drawRect:
方法寸癌,而UIView
將會(huì)管理一切,這包括當(dāng)圖層需要被重新繪制時(shí)自動(dòng)調(diào)用其中的-display
方法弱贼。
總結(jié)
這一章節(jié)講述了圖層的主圖像以及它的相關(guān)屬性蒸苇。你學(xué)習(xí)了如何移動(dòng)和裁剪圖像、從一個(gè)精靈圖集中裁剪出獨(dú)立的圖像以及在使用CALayerDelegate
和Core Graphics
時(shí)繪制圖層內(nèi)容吮旅。
在第3章“圖層幾何”中溪烤,我們將查看一個(gè)圖層的幾何并測(cè)試相關(guān)圖層之間如何重新設(shè)置位置和大小。