iOS核心動(dòng)畫高級(jí)技巧學(xué)習(xí)---寄宿圖

我們?cè)诘谝徽隆簣D層樹』中介紹了CALayer類并創(chuàng)建了一個(gè)簡(jiǎn)單的有藍(lán)色背景的圖層疚顷。背景顏色還好啦,但是如果它僅僅是展現(xiàn)了一個(gè)單調(diào)的顏色未免也太無(wú)聊了峦朗。事實(shí)上CALayer類能夠包含一張你喜歡的圖片建丧,這一章節(jié)我們將來(lái)探索CALayer的寄宿圖(即圖層中包含的圖)。

contents屬性

CALayer 有一個(gè)屬性叫做contents波势,這個(gè)屬性的類型被定義為id翎朱,意味著它可以是任何類型的對(duì)象。在這種情況下尺铣,你可以給contents屬性賦任何值拴曲,你的app仍然能夠編譯通過(guò)。但是凛忿,在實(shí)踐中澈灼,如果你給contents賦的不是CGImage,那么你得到的圖層將是空白的店溢。
contents這個(gè)奇怪的表現(xiàn)是由Mac OS的歷史原因造成的叁熔。它之所以被定義為id類型,是因?yàn)樵贛ac OS系統(tǒng)上逞怨,這個(gè)屬性對(duì)CGImage和NSImage類型的值都起作用者疤。如果你試圖在iOS平臺(tái)上將UIImage的值賦給它福澡,只能得到一個(gè)空白的圖層叠赦。一些初識(shí)Core Animation的iOS開發(fā)者可能會(huì)對(duì)這個(gè)感到困惑。
頭疼的不僅僅是我們剛才提到的這個(gè)問(wèn)題。事實(shí)上除秀,你真正要賦值的類型應(yīng)該是CGImageRef糯累,它是一個(gè)指向CGImage結(jié)構(gòu)的指針。UIImage有一個(gè)CGImage屬性册踩,它返回一個(gè)"CGImageRef",如果你想把這個(gè)值直接賦值給CALayer的contents泳姐,那你將會(huì)得到一個(gè)編譯錯(cuò)誤。因?yàn)镃GImageRef并不是一個(gè)真正的Cocoa對(duì)象暂吉,而是一個(gè)Core Foundation類型胖秒。
盡管Core Foundation類型跟Cocoa對(duì)象在運(yùn)行時(shí)貌似很像(被稱作toll-free bridging),他們并不是類型兼容的慕的,不過(guò)你可以通過(guò)bridged關(guān)鍵字轉(zhuǎn)換阎肝。如果要給圖層的寄宿圖賦值,你可以按照以下這個(gè)方法:

layer.contents = (__bridge id)image.CGImage;

如果你沒有使用ARC(自動(dòng)引用計(jì)數(shù))肮街,你就不需要__bridge這部分风题。但是,你干嘛不用ARC嫉父?沛硅!
讓我們來(lái)繼續(xù)修改我們?cè)诘谝徽滦陆ǖ墓こ蹋员隳軌蛘故疽粡垐D片而不僅僅是一個(gè)背景色绕辖。我們已經(jīng)用代碼的方式建立一個(gè)圖層摇肌,那我們就不需要額外的圖層了。那么我們就直接把layerView的宿主圖層的contents屬性設(shè)置成圖片仪际。
清單2.1 更新后的代碼朦蕴。

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad]; //load an image
  UIImage *image = [UIImage imageNamed:@"Snowman.png"];

  //add it directly to our view's layer
  self.layerView.layer.contents = (__bridge id)image.CGImage;
}
@end

圖表2.1 在UIView的宿主圖層中顯示一張圖片

2.1.png

我們用這些簡(jiǎn)單的代碼做了一件很有趣的事情:我們利用CALayer在一個(gè)普通的UIView中顯示了一張圖片。這不是一個(gè)UIImageView弟头,它不是我們通常用來(lái)展示圖片的方法吩抓。通過(guò)直接操作圖層,我們使用了一些新的函數(shù)赴恨,使得UIView更加有趣了疹娶。

contentGravity

你可能已經(jīng)注意到了我們的雪人看起來(lái)有點(diǎn)。伦连。雨饺。胖 ==! 我們加載的圖片并不剛好是一個(gè)方的惑淳,為了適應(yīng)這個(gè)視圖额港,它有一點(diǎn)點(diǎn)被拉伸了。在使用UIImageView的時(shí)候遇到過(guò)同樣的問(wèn)題歧焦,解決方法就是把contentMode屬性設(shè)置成更合適的值移斩,像這樣:

view.contentMode = UIViewContentModeScaleAspectFit;

這個(gè)方法基本和我們遇到的情況的解決方法已經(jīng)接近了(你可以試一下 :) ),不過(guò)UIView大多數(shù)視覺相關(guān)的屬性比如contentMode,對(duì)這些屬性的操作其實(shí)是對(duì)對(duì)應(yīng)圖層的操作向瓷。+

CALayer與contentMode對(duì)應(yīng)的屬性叫做contentsGravity肠套,但是它是一個(gè)NSString類型,而不是像對(duì)應(yīng)的UIKit部分猖任,那里面的值是枚舉你稚。contentsGravity可選的常量值有以下一些:

kCAGravityCenter
kCAGravityTop
kCAGravityBottom
kCAGravityLeft
kCAGravityRight
kCAGravityTopLeft
kCAGravityTopRight
kCAGravityBottomLeft
kCAGravityBottomRight
kCAGravityResize
kCAGravityResizeAspect
kCAGravityResizeAspectFill

和cotentMode一樣,contentsGravity的目的是為了決定內(nèi)容在圖層的邊界中怎么對(duì)齊朱躺,我們將使用kCAGravityResizeAspect刁赖,它的效果等同于UIViewContentModeScaleAspectFit长搀, 同時(shí)它還能在圖層中等比例拉伸以適應(yīng)圖層的邊界。

self.layerView.layer.contentsGravity = kCAGravityResizeAspect;

圖2.2 可以看到結(jié)果


2.2.png

圖2.2 正確地設(shè)置contentsGravity的值

contentsScale

contentsScale屬性定義了寄宿圖的像素尺寸和視圖大小的比例涯肩,默認(rèn)情況下它是一個(gè)值為1.0的浮點(diǎn)數(shù)。
contentsScale的目的并不是那么明顯病苗。它并不是總會(huì)對(duì)屏幕上的寄宿圖有影響症汹。如果你嘗試對(duì)我們的例子設(shè)置不同的值,你就會(huì)發(fā)現(xiàn)根本沒任何影響咬展。因?yàn)閏ontents由于設(shè)置了contentsGravity屬性,所以它已經(jīng)被拉伸以適應(yīng)圖層的邊界破婆。
如果你只是單純地想放大圖層的contents圖片,你可以通過(guò)使用圖層的transform和affineTransform屬性來(lái)達(dá)到這個(gè)目的(見第五章『Transforms』胸囱,里面對(duì)此有解釋)祷舀,這(指放大)也不是contengsScale的目的所在.
contentsScale屬性其實(shí)屬于支持高分辨率(又稱Hi-DPI或Retina)屏幕機(jī)制的一部分。它用來(lái)判斷在繪制圖層的時(shí)候應(yīng)該為寄宿圖創(chuàng)建的空間大小烹笔,和需要顯示的圖片的拉伸度(假設(shè)并沒有設(shè)置contentsGravity屬性)裳扯。UIView有一個(gè)類似功能但是非常少用到的contentScaleFactor屬性。
如果contentsScale設(shè)置為1.0谤职,將會(huì)以每個(gè)點(diǎn)1個(gè)像素繪制圖片饰豺,如果設(shè)置為2.0,則會(huì)以每個(gè)點(diǎn)2個(gè)像素繪制圖片允蜈,這就是我們熟知的Retina屏幕冤吨。(如果你對(duì)像素和點(diǎn)的概念不是很清楚的話蒿柳,這個(gè)章節(jié)的后面部分將會(huì)對(duì)此做出解釋)。
這并不會(huì)對(duì)我們?cè)谑褂胟CAGravityResizeAspect時(shí)產(chǎn)生任何影響锅很,因?yàn)樗褪抢靾D片以適應(yīng)圖層而已其馏,根本不會(huì)考慮到分辨率問(wèn)題凤跑。但是如果我們把contentsGravity設(shè)置為kCAGravityCenter(這個(gè)值并不會(huì)拉伸圖片)爆安,那將會(huì)有很明顯的變化(如圖2.3)


2.3.png

圖2.3 用錯(cuò)誤的contentsScale屬性顯示Retina圖片
如你所見,我們的雪人不僅有點(diǎn)大還有點(diǎn)像素的顆粒感仔引。那是因?yàn)楹蚒IImage不同扔仓,CGImage沒有拉伸的概念。當(dāng)我們使用UIImage類去讀取我們的雪人圖片的時(shí)候咖耘,他讀取了高質(zhì)量的Retina版本的圖片翘簇。但是當(dāng)我們用CGImage來(lái)設(shè)置我們的圖層的內(nèi)容時(shí),拉伸這個(gè)因素在轉(zhuǎn)換的時(shí)候就丟失了儿倒。不過(guò)我們可以通過(guò)手動(dòng)設(shè)置contentsScale來(lái)修復(fù)這個(gè)問(wèn)題(如2.2清單)版保,圖2.4是結(jié)果

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad]; //load an image
  UIImage *image = [UIImage imageNamed:@"Snowman.png"]; //add it directly to our view's layer
  self.layerView.layer.contents = (__bridge id)image.CGImage; //center the image
  self.layerView.layer.contentsGravity = kCAGravityCenter;

  //set the contentsScale to match image
  self.layerView.layer.contentsScale = image.scale;
}

@end
2.4.png

圖2.4 同樣的Retina圖片設(shè)置了正確的contentsScale之后
當(dāng)用代碼的方式來(lái)處理寄宿圖的時(shí)候,一定要記住要手動(dòng)的設(shè)置圖層的contentsScale屬性夫否,否則,你的圖片在Retina設(shè)備上就顯示得不正確啦汞幢。代碼如下:
layer.contentsScale = [UIScreen mainScreen].scale;

maskToBounds

現(xiàn)在我們的雪人總算是顯示了正確的大小森篷,不過(guò)你也許已經(jīng)發(fā)現(xiàn)了另外一些事情:他超出了視圖的邊界。默認(rèn)情況下姻氨,UIView仍然會(huì)繪制超過(guò)邊界的內(nèi)容或是子視圖岩馍,在CALayer下也是這樣的蛀恩。
UIView有一個(gè)叫做clipsToBounds的屬性可以用來(lái)決定是否顯示超出邊界的內(nèi)容双谆,CALayer對(duì)應(yīng)的屬性叫做masksToBounds顽馋,把它設(shè)置為YES寸谜,雪人就在邊界里啦~(如圖2.5)

2.5.png

圖2.5 使用masksToBounds來(lái)修建圖層內(nèi)容

contentsRect

CALayer的contentsRect屬性允許我們?cè)趫D層邊框里顯示寄宿圖的一個(gè)子域他爸。這涉及到圖片是如何顯示和拉伸的诊笤,所以要比contentsGravity靈活多了
和bounds讨跟,frame不同晾匠,contentsRect不是按點(diǎn)來(lái)計(jì)算的混聊,它使用了單位坐標(biāo),單位坐標(biāo)指定在0到1之間沟于,是一個(gè)相對(duì)值(像素和點(diǎn)就是絕對(duì)值)展懈。所以他們是相對(duì)與寄宿圖的尺寸的存崖。iOS使用了以下的坐標(biāo)系統(tǒng):
點(diǎn) —— 在iOS和Mac OS中最常見的坐標(biāo)體系来惧。點(diǎn)就像是虛擬的像素供搀,也被稱作邏輯像素葛虐。在標(biāo)準(zhǔn)設(shè)備上屿脐,一個(gè)點(diǎn)就是一個(gè)像素摄悯,但是在Retina設(shè)備上奢驯,一個(gè)點(diǎn)等于2*2個(gè)像素瘪阁。iOS用點(diǎn)作為屏幕的坐標(biāo)測(cè)算體系就是為了在Retina設(shè)備和普通設(shè)備上能有一致的視覺效果。
像素 —— 物理像素坐標(biāo)并不會(huì)用來(lái)屏幕布局禾进,但是仍然與圖片有相對(duì)關(guān)系艇拍。UIImage是一個(gè)屏幕分辨率解決方案卸夕,所以指定點(diǎn)來(lái)度量大小快集。但是一些底層的圖片表示如CGImage就會(huì)使用像素个初,所以你要清楚在Retina設(shè)備和普通設(shè)備上院溺,他們表現(xiàn)出來(lái)了不同的大小覆获。
單位 —— 對(duì)于與圖片大小或是圖層邊界相關(guān)的顯示,單位坐標(biāo)是一個(gè)方便的度量方式痊班, 當(dāng)大小改變的時(shí)候涤伐,也不需要再次調(diào)整凝果。單位坐標(biāo)在OpenGL這種紋理坐標(biāo)系統(tǒng)中用得很多器净,Core Animation中也用到了單位坐標(biāo)山害。
默認(rèn)的contentsRect是{0, 0, 1, 1}浪慌,這意味著整個(gè)寄宿圖默認(rèn)都是可見的,如果我們指定一個(gè)小一點(diǎn)的矩形乌妒,圖片就會(huì)被裁剪(如圖2.6)

2.6.png

圖2.6 一個(gè)自定義的contentsRect(左)和之前顯示的內(nèi)容(右)
事實(shí)上給contentsRect設(shè)置一個(gè)負(fù)數(shù)的原點(diǎn)或是大于{1, 1}的尺寸也是可以的欧宜。這種情況下冗茸,最外面的像素會(huì)被拉伸以填充剩下的區(qū)域夏漱。
contentsRect在app中最有趣的地方在于一個(gè)叫做image sprites(圖片拼合)的用法挂绰。如果你有游戲編程的經(jīng)驗(yàn)葵蒂,那么你一定對(duì)圖片拼合的概念很熟悉践付,圖片能夠在屏幕上獨(dú)立地變更位置隧土。拋開游戲編程不談曹傀,這個(gè)技術(shù)常用來(lái)指代載入拼合的圖片皆愉,跟移動(dòng)圖片一點(diǎn)關(guān)系也沒有亥啦。
典型地,圖片拼合后可以打包整合到一張大圖上一次性載入媒鼓。相比多次載入不同的圖片错妖,這樣做能夠帶來(lái)很多方面的好處:內(nèi)存使用暂氯,載入時(shí)間痴施,渲染性能等等
2D游戲引擎入Cocos2D使用了拼合技術(shù)动遭,它使用OpenGL來(lái)顯示圖片厘惦。不過(guò)我們可以使用拼合在一個(gè)普通的UIKit應(yīng)用中施绎,對(duì)羡玛!就是使用contentsRect
首先缝左,我們需要一個(gè)拼合后的圖表 —— 一個(gè)包含小一些的拼合圖的大圖片蛇数。如圖2.7所示:

2.7.png

接下來(lái)耳舅,我們要在app中載入并顯示這些拼合圖。規(guī)則很簡(jiǎn)單:像平常一樣載入我們的大圖盔性,然后把它賦值給四個(gè)獨(dú)立的圖層的contents冕香,然后設(shè)置每個(gè)圖層的contentsRect來(lái)去掉我們不想顯示的部分悉尾。
我們的工程中需要一些額外的視圖构眯。(為了避免太多代碼。我們將使用Interface Builder來(lái)拜訪他們的位置拄衰,如果你愿意還是可以用代碼的方式來(lái)實(shí)現(xiàn)的)茫打。清單2.3有需要的代碼老赤,圖2.8展示了結(jié)果

@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *coneView;
@property (nonatomic, weak) IBOutlet UIView *shipView;
@property (nonatomic, weak) IBOutlet UIView *iglooView;
@property (nonatomic, weak) IBOutlet UIView *anchorView;
@end

@implementation ViewController

- (void)addSpriteImage:(UIImage *)image withContentRect:(CGRect)rect toLayer:(CALayer *)layer //set image
{
  layer.contents = (__bridge id)image.CGImage;

  //scale contents to fit
  layer.contentsGravity = kCAGravityResizeAspect;

  //set contentsRect
  layer.contentsRect = rect;
}

- (void)viewDidLoad 
{
  [super viewDidLoad]; //load sprite sheet
  UIImage *image = [UIImage imageNamed:@"Sprites.png"];
  //set igloo sprite
  [self addSpriteImage:image withContentRect:CGRectMake(0, 0, 0.5, 0.5) toLayer:self.iglooView.layer];
  //set cone sprite
  [self addSpriteImage:image withContentRect:CGRectMake(0.5, 0, 0.5, 0.5) toLayer:self.coneView.layer];
  //set anchor sprite
  [self addSpriteImage:image withContentRect:CGRectMake(0, 0.5, 0.5, 0.5) toLayer:self.anchorView.layer];
  //set spaceship sprite
  [self addSpriteImage:image withContentRect:CGRectMake(0.5, 0.5, 0.5, 0.5) toLayer:self.shipView.layer];
}
@end
2.8.png

拼合不僅給app提供了一個(gè)整潔的載入方式,還有效地提高了載入性能(單張大圖比多張小圖載入地更快)汉柒,但是如果有手動(dòng)安排的話碾褂,他們還是有一些不方便的,如果你需要在一個(gè)已經(jīng)創(chuàng)建好的品和圖上做一些尺寸上的修改或者其他變動(dòng)乓诽,無(wú)疑是比較麻煩的咒程。
Mac上有一些商業(yè)軟件可以為你自動(dòng)拼合圖片鸠天,這些工具自動(dòng)生成一個(gè)包含拼合后的坐標(biāo)的XML或者plist文件,拼合圖片的使用大大簡(jiǎn)化孵坚。這個(gè)文件可以和圖片一同載入粮宛,并給每個(gè)拼合的圖層設(shè)置contentsRect
,這樣開發(fā)者就不用手動(dòng)寫代碼來(lái)擺放位置了卖宠。
這些文件通常在OpenGL游戲中使用,不過(guò)呢忧饭,你要是有興趣在一些常見的app中使用拼合技術(shù)扛伍,那么一個(gè)叫做LayerSprites的開源庫(kù)(https://github.com/nicklockwood/LayerSprites)词裤,它能夠讀取Cocos2D格式中的拼合圖并在普通的Core Animation層中顯示出來(lái)渔肩。

contentsCenter

本章我們介紹的最后一個(gè)和內(nèi)容有關(guān)的屬性是contentsCenter,看名字你可能會(huì)以為它可能跟圖片的位置有關(guān)钳踊,不過(guò)這名字著實(shí)誤導(dǎo)了你吴藻。contentsCenter其實(shí)是一個(gè)CGRect航罗,它定義了一個(gè)固定的邊框和一個(gè)在圖層上可拉伸的區(qū)域复亏。 改變contentsCenter的值并不會(huì)影響到寄宿圖的顯示笤成,除非這個(gè)圖層的大小改變了培遵,你才看得到效果节仿。
默認(rèn)情況下,contentsCenter是{0, 0, 1, 1},這意味著如果大形呤恪(由conttensGravity決定)改變了,那么寄宿圖將會(huì)均勻地拉伸開拐迁。但是如果我們?cè)黾釉c(diǎn)的值并減小尺寸。我們會(huì)在圖片的周圍創(chuàng)造一個(gè)邊框患雏。圖2.9展示了contentsCenter設(shè)置為{0.25, 0.25, 0.5, 0.5}的效果颜阐。

2.9.png

圖2.9 contentsCenter的例子
這意味著我們可以隨意重設(shè)尺寸均蜜,邊框仍然會(huì)是連續(xù)的德玫。他工作起來(lái)的效果和UIImage里的-resizableImageWithCapInsets: 方法效果非常類似键兜,只是它可以運(yùn)用到任何寄宿圖,甚至包括在Core Graphics運(yùn)行時(shí)繪制的圖形(本章稍后會(huì)講到)坐桩。

2.10.png

圖2.10 同一圖片使用不同的contentsCenter
清單2.4 演示了如何編寫這些可拉伸視圖。不過(guò)备徐,contentsCenter的另一個(gè)很酷的特性就是肩豁,它可以在Interface Builder里面配置匾寝,根本不用寫代碼。如圖2.11
清單2.4 用contentsCenter設(shè)置可拉伸視圖

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *button1;
@property (nonatomic, weak) IBOutlet UIView *button2;

@end

@implementation ViewController

- (void)addStretchableImage:(UIImage *)image withContentCenter:(CGRect)rect toLayer:(CALayer *)layer
{  
  //set image
  layer.contents = (__bridge id)image.CGImage;

  //set contentsCenter
  layer.contentsCenter = rect;
}

- (void)viewDidLoad
{
  [super viewDidLoad]; //load button image
  UIImage *image = [UIImage imageNamed:@"Button.png"];

  //set button 1
  [self addStretchableImage:image withContentCenter:CGRectMake(0.25, 0.25, 0.5, 0.5) toLayer:self.button1.layer];

  //set button 2
  [self addStretchableImage:image withContentCenter:CGRectMake(0.25, 0.25, 0.5, 0.5) toLayer:self.button2.layer];
}

@end
2.11.png

圖2.11 用Interface Builder 探測(cè)窗口控制contentsCenter屬性

Custom Drawing

給contents賦CGImage的值不是唯一的設(shè)置寄宿圖的方法锹杈。我們也可以直接用Core Graphics直接繪制寄宿圖裕菠。能夠通過(guò)繼承UIView并實(shí)現(xiàn)-drawRect:方法來(lái)自定義繪制雀扶。
-drawRect: 方法沒有默認(rèn)的實(shí)現(xiàn),因?yàn)閷?duì)UIView來(lái)說(shuō)村象,寄宿圖并不是必須的志膀,它不在意那到底是單調(diào)的顏色還是有一個(gè)圖片的實(shí)例。如果UIView檢測(cè)到-drawRect: 方法被調(diào)用了竭缝,它就會(huì)為視圖分配一個(gè)寄宿圖,這個(gè)寄宿圖的像素尺寸等于視圖大小乘以 contentsScale的值沼瘫。
如果你不需要寄宿圖抬纸,那就不要?jiǎng)?chuàng)建這個(gè)方法了,這會(huì)造成CPU資源和內(nèi)存的浪費(fèi)耿戚,這也是為什么蘋果建議:如果沒有自定義繪制的任務(wù)就不要在子類中寫一個(gè)空的-drawRect:方法湿故。
當(dāng)視圖在屏幕上出現(xiàn)的時(shí)候 -drawRect:方法就會(huì)被自動(dòng)調(diào)用。-drawRect:方法里面的代碼利用Core Graphics去繪制一個(gè)寄宿圖膜蛔,然后內(nèi)容就會(huì)被緩存起來(lái)直到它需要被更新(通常是因?yàn)殚_發(fā)者調(diào)用了-setNeedsDisplay方法坛猪,盡管影響到表現(xiàn)效果的屬性值被更改時(shí),一些視圖類型會(huì)被自動(dòng)重繪皂股,如bounds屬性)墅茉。雖然-drawRect:方法是一個(gè)UIView方法,事實(shí)上都是底層的CALayer安排了重繪工作和保存了因此產(chǎn)生的圖片。
CALayer有一個(gè)可選的delegate屬性就斤,實(shí)現(xiàn)了CALayerDelegate協(xié)議悍募,當(dāng)CALayer需要一個(gè)內(nèi)容特定的信息時(shí),就會(huì)從協(xié)議中請(qǐng)求战转。CALayerDelegate是一個(gè)非正式協(xié)議搜立,其實(shí)就是說(shuō)沒有CALayerDelegate @protocol可以讓你在類里面引用啦。你只需要調(diào)用你想調(diào)用的方法槐秧,CALayer會(huì)幫你做剩下的。(delegate屬性被聲明為id類型忧设,所有的代理方法都是可選的)刁标。
當(dāng)需要被重繪時(shí),CALayer會(huì)請(qǐng)求它的代理給他一個(gè)寄宿圖來(lái)顯示址晕。它通過(guò)調(diào)用下面這個(gè)方法做到的:

(void)displayLayer:(CALayerCALayer *)layer;

趁著這個(gè)機(jī)會(huì)膀懈,如果代理想直接設(shè)置contents屬性的話,它就可以這么做谨垃,不然沒有別的方法可以調(diào)用了启搂。如果代理不實(shí)現(xiàn)-displayLayer:方法,CALayer就會(huì)轉(zhuǎn)而嘗試調(diào)用下面這個(gè)方法:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
在調(diào)用這個(gè)方法之前刘陶,CALayer創(chuàng)建了一個(gè)合適尺寸的空寄宿圖(尺寸由bounds和contentsScale決定)和一個(gè)Core Graphics的繪制上下文環(huán)境胳赌,為繪制寄宿圖做準(zhǔn)備,他作為ctx參數(shù)傳入匙隔。
讓我們來(lái)繼續(xù)第一章的項(xiàng)目讓它實(shí)現(xiàn)CALayerDelegate并做一些繪圖工作吧(見清單2.5).圖2.12是他的結(jié)果
清單2.5 實(shí)現(xiàn)CALayerDelegate

@implementation ViewController
- (void)viewDidLoad
{
  [super viewDidLoad];
  
  //create sublayer
  CALayer *blueLayer = [CALayer layer];
  blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
  blueLayer.backgroundColor = [UIColor blueColor].CGColor;

  //set controller as layer delegate
  blueLayer.delegate = self;

  //ensure that layer backing image uses correct scale
  blueLayer.contentsScale = [UIScreen mainScreen].scale; //add layer to our view
  [self.layerView.layer addSublayer:blueLayer];

  //force layer to redraw
  [blueLayer display];
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
  //draw a thick red circle
  CGContextSetLineWidth(ctx, 10.0f);
  CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
  CGContextStrokeEllipseInRect(ctx, layer.bounds);
}
@end

2.12.png

圖2.12 實(shí)現(xiàn)CALayerDelegate來(lái)繪制圖層
注意一下一些有趣的事情:
我們?cè)赽lueLayer上顯式地調(diào)用了-display疑苫。不同于UIView,當(dāng)圖層顯示在屏幕上時(shí)纷责,CALayer不會(huì)自動(dòng)重繪它的內(nèi)容捍掺。它把重繪的決定權(quán)交給了開發(fā)者。
盡管我們沒有用masksToBounds屬性再膳,繪制的那個(gè)圓仍然沿邊界被裁剪了挺勿。這是因?yàn)楫?dāng)你使用CALayerDelegate繪制寄宿圖的時(shí)候,并沒有對(duì)超出邊界外的內(nèi)容提供繪制支持喂柒。
現(xiàn)在你理解了CALayerDelegate不瓶,并知道怎么使用它。但是除非你創(chuàng)建了一個(gè)單獨(dú)的圖層胳喷,你幾乎沒有機(jī)會(huì)用到CALayerDelegate協(xié)議湃番。因?yàn)楫?dāng)UIView創(chuàng)建了它的宿主圖層時(shí),它就會(huì)自動(dòng)地把圖層的delegate設(shè)置為它自己吭露,并提供了一個(gè)-displayLayer:的實(shí)現(xiàn)吠撮,那所有的問(wèn)題就都沒了。
當(dāng)使用寄宿了視圖的圖層的時(shí)候,你也不必實(shí)現(xiàn)-displayLayer:和-drawLayer:inContext:方法來(lái)繪制你的寄宿圖泥兰。通常做法是實(shí)現(xiàn)UIView的-drawRect:方法弄屡,UIView就會(huì)幫你做完剩下的工作,包括在需要重繪的時(shí)候調(diào)用-display方法鞋诗。

總結(jié)

本章介紹了寄宿圖和一些相關(guān)的屬性膀捷。你學(xué)到了如何顯示和放置圖片, 使用拼合技術(shù)來(lái)顯示削彬, 以及用CALayerDelegate和Core Graphics來(lái)繪制圖層內(nèi)容全庸。 在第三章,"圖層幾何學(xué)"中融痛,我們將會(huì)探討一下圖層的幾何壶笼,觀察他們是如何放置和改變相互的尺寸的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雁刷,一起剝皮案震驚了整個(gè)濱河市覆劈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沛励,老刑警劉巖责语,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異目派,居然都是意外死亡坤候,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門址貌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)铐拐,“玉大人,你說(shuō)我怎么就攤上這事练对”轶” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵螟凭,是天一觀的道長(zhǎng)虚青。 經(jīng)常有香客問(wèn)我,道長(zhǎng)螺男,這世上最難降的妖魔是什么棒厘? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮下隧,結(jié)果婚禮上奢人,老公的妹妹穿的比我還像新娘。我一直安慰自己淆院,他們只是感情好何乎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般支救。 火紅的嫁衣襯著肌膚如雪抢野。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天各墨,我揣著相機(jī)與錄音指孤,去河邊找鬼。 笑死贬堵,一個(gè)胖子當(dāng)著我的面吹牛恃轩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扁瓢,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼详恼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了引几?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤挽铁,失蹤者是張志新(化名)和其女友劉穎伟桅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叽掘,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡楣铁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了更扁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盖腕。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖浓镜,靈堂內(nèi)的尸體忽然破棺而出溃列,到底是詐尸還是另有隱情,我是刑警寧澤膛薛,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布听隐,位于F島的核電站,受9級(jí)特大地震影響哄啄,放射性物質(zhì)發(fā)生泄漏雅任。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一咨跌、第九天 我趴在偏房一處隱蔽的房頂上張望沪么。 院中可真熱鬧,春花似錦锌半、人聲如沸禽车。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)哭当。三九已至猪腕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钦勘,已是汗流浹背陋葡。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留彻采,地道東北人腐缤。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像肛响,于是被迫代替她去往敵國(guó)和親岭粤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

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