I.2 主圖像

一圖勝千言周瞎,一接口勝千圖渊迁。——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
}
圖2.1 一張顯示在UIView主圖層中的圖像

這是非常簡(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é)果辽俗。


圖2.2 用正確的contentsGravity顯示的雪人圖像

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)。

圖2.3 一個(gè)被錯(cuò)誤的contentsScale默認(rèn)顯示的Retina圖像

正如你所見掀宋,我們的雪人非常大而且像素化深纲。這是因?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
}
圖2.4 顯示同一張?jiān)O(shè)置了正確contentsScale的Retina圖像

當(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)痹屹。

圖2.5 使用masksToBounds來裁剪圖層內(nèi)容

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)。

圖2.6 一個(gè)自定義contentsRect(左)和被顯示的圖像(右)

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è)精靈圖集的案例。

圖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)
    }
}
圖2.8 四個(gè)精靈坪仇,隨機(jī)分布在屏幕上

精靈圖集是一個(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ù)的縮放效果的勾习。

圖2.9 一個(gè)contentsCenter矩形的例子以及它對(duì)圖像的影響

這意味著當(dāng)我們直接重新調(diào)整視圖大小的時(shí)候浓瞪,邊框?qū)⒈3忠恢拢ㄈ鐖D2.10)。這與使用UIImage-resizableImageWithCapInsets:方法相似巧婶,但可以應(yīng)用于任一圖層的主圖像乾颁,甚至包換在運(yùn)行時(shí)用Core Graphics繪制的圖層(正如這章后面將講述的一樣)。[5]

圖2.10 一對(duì)使用同一張可拉伸圖像的視圖

表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)
    }
}
圖2.11 Interface Builder檢查器中contentsCenter的控制(最下方的Stretching)

自定義繪圖

用一張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)
    }
}
圖2.12 使用CALayerDelegate繪制主圖像的圖層

一些有趣的注意事項(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ú)立的圖像以及在使用CALayerDelegateCore Graphics時(shí)繪制圖層內(nèi)容吮旅。

在第3章“圖層幾何”中溪烤,我們將查看一個(gè)圖層的幾何并測(cè)試相關(guān)圖層之間如何重新設(shè)置位置和大小。


  1. 可以類比為swift中的AnyObject ?

  2. 這是objective-c的技術(shù)庇勃,swift并不需要 ?

  3. 文章中使用的雪人圖片


    Snowman.png
    ?

  4. 文章中使用的精靈圖集


    Sprites.png
    ?

  5. 文章中使用的按鈕


    Button.png
    ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末檬嘀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子责嚷,更是在濱河造成了極大的恐慌鸳兽,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罕拂,死亡現(xiàn)場(chǎng)離奇詭異揍异,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)爆班,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門衷掷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人柿菩,你說我怎么就攤上這事戚嗅。” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵懦胞,是天一觀的道長(zhǎng)替久。 經(jīng)常有香客問我,道長(zhǎng)医瘫,這世上最難降的妖魔是什么侣肄? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮醇份,結(jié)果婚禮上稼锅,老公的妹妹穿的比我還像新娘。我一直安慰自己僚纷,他們只是感情好矩距,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著怖竭,像睡著了一般锥债。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上痊臭,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天哮肚,我揣著相機(jī)與錄音,去河邊找鬼广匙。 笑死允趟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸦致。 我是一名探鬼主播潮剪,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼分唾!你這毒婦竟也來了抗碰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤绽乔,失蹤者是張志新(化名)和其女友劉穎弧蝇,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體折砸,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡看疗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鞍爱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡专酗,死狀恐怖睹逃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤沉填,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布疗隶,位于F島的核電站,受9級(jí)特大地震影響翼闹,放射性物質(zhì)發(fā)生泄漏斑鼻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一猎荠、第九天 我趴在偏房一處隱蔽的房頂上張望坚弱。 院中可真熱鬧,春花似錦关摇、人聲如沸荒叶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽些楣。三九已至,卻和暖如春宪睹,著一層夾襖步出監(jiān)牢的瞬間愁茁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工亭病, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹅很,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓命贴,卻偏偏與公主長(zhǎng)得像道宅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子胸蛛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容