iOS繪圖框架CoreGraphics分析

由于CoreGraphics框架有太多的API雄可,對(duì)于初次接觸或者對(duì)該框架不是十分了解的人凿傅,在繪圖時(shí),對(duì)API的選擇會(huì)感到有些迷茫滞项,甚至?xí)X得iOS的圖形繪制有些繁瑣狭归。因此,本文主要介紹一下iOS的繪圖方法和分析一下CoreGraphics框架的繪圖原理文判。

一、繪圖系統(tǒng)簡(jiǎn)介

iOS的繪圖框架有多種室梅,我們平常最常用的就是UIKit戏仓,其底層是依賴CoreGraphics實(shí)現(xiàn)的,而且絕大多數(shù)的圖形界面也都是由UIKit完成亡鼠,并且UIImage赏殃、NSStringUIBezierPath间涵、UIColor等都知道如何繪制自己仁热,也提供了一些方法來滿足我們常用的繪圖需求。除了UIKit勾哩,還有CoreGraphics抗蠢、Core AnimationCore Image思劳,OpenGL ES等多種框架迅矛,來滿足不同的繪圖要求。各個(gè)框架的大概介紹如下:

  • UIKit:最常用的視圖框架潜叛,封裝度最高秽褒,都是OC對(duì)象
  • CoreGraphics:主要繪圖系統(tǒng),常用于繪制自定義視圖威兜,純C的API销斟,使用Quartz2D做引擎
  • CoreAnimation:提供強(qiáng)大的2D和3D動(dòng)畫效果
  • CoreImage:給圖片提供各種濾鏡處理,比如高斯模糊椒舵、銳化等
  • OpenGL-ES:主要用于游戲繪制蚂踊,但它是一套編程規(guī)范,具體由設(shè)備制造商實(shí)現(xiàn)

繪圖系統(tǒng)

圖1

二逮栅、繪圖方式

實(shí)際的繪圖包括兩部分:視圖繪制視圖布局悴势,它們實(shí)現(xiàn)的功能是不同的窗宇,在理解這兩個(gè)概念之前,需要了解一下什么是繪圖周期特纤,因?yàn)槎际窃诶L圖周期中進(jìn)行繪制的军俊。

繪圖周期

  • iOS在運(yùn)行循環(huán)中會(huì)整合所有的繪圖請(qǐng)求,并一次將它們繪制出來
  • 不能在子線程中繪制捧存,也不能進(jìn)行復(fù)雜的操作粪躬,否則會(huì)造成主線程卡頓

1.視圖繪制

調(diào)用UIViewdrawRect:方法進(jìn)行繪制。如果調(diào)用一個(gè)視圖的setNeedsDisplay方法昔穴,那么該視圖就被標(biāo)記為重新繪制镰官,并且會(huì)在下一次繪制周期中重新繪制,自動(dòng)調(diào)用drawRect:方法吗货。

2.視圖布局

調(diào)用UIViewlayoutSubviews方法泳唠。如果調(diào)用一個(gè)視圖的setNeedsLayout方法,那么該視圖就被標(biāo)記為需要重新布局宙搬,UIKit會(huì)自動(dòng)調(diào)用layoutSubviews方法及其子視圖的layoutSubviews方法笨腥。

在繪圖時(shí),我們應(yīng)該盡量多使用布局勇垛,少使用繪制脖母,是因?yàn)椴季质褂玫氖?code>GPU,而繪制使用的是CPU闲孤。GPU對(duì)于圖形處理有優(yōu)勢(shì)谆级,而CPU要處理的事情較多,且不擅長(zhǎng)處理圖形讼积,所以盡量使用GPU來處理圖形肥照。

三、繪圖狀態(tài)切換

iOS的繪圖有多種對(duì)應(yīng)的狀態(tài)切換币砂,比如:pop/push建峭、save/restorecontext/imageContextCGPathRef/UIBezierPath等决摧,下面分別進(jìn)行介紹:

1.pop / push

設(shè)置繪圖的上下文環(huán)境(context)

push:UIGraphicsPushContext(context)把context壓入棧中亿蒸,并把context設(shè)置為當(dāng)前繪圖上下文

pop:UIGraphicsPopContext將棧頂?shù)纳舷挛膹棾觯謴?fù)先前的上下文掌桩,但是繪圖狀態(tài)不變

下面繪制的視圖是黑色

- (void)drawRect:(CGRect)rect {
    [[UIColor redColor] setFill];
    UIGraphicsPushContext(UIGraphicsGetCurrentContext());
    [[UIColor blackColor] setFill];
    UIGraphicsPopContext();
    UIRectFill(CGRectMake(90, 340, 100, 100)); // black color
}

2.save / restore

設(shè)置繪圖的狀態(tài)(state)

save:CGContextSaveGState 壓棧當(dāng)前的繪圖狀態(tài)边锁,僅僅是繪圖狀態(tài),不是繪圖上下文

restore:恢復(fù)剛才保存的繪圖狀態(tài)

下面繪制的視圖是紅色

- (void)drawRect:(CGRect)rect {
    [[UIColor redColor] setFill];
    CGContextSaveGState(UIGraphicsGetCurrentContext());
    [[UIColor blackColor] setFill];
    CGContextRestoreGState(UIGraphicsGetCurrentContext());
    UIRectFill(CGRectMake(90, 200, 100, 100)); // red color
}

3.context / imageContext

iOS的繪圖必須在一個(gè)上下文中繪制波岛,所以在繪圖之前要獲取一個(gè)上下文茅坛。如果是繪制圖片,就需要獲取一個(gè)圖片的上下文;如果是繪制其它視圖贡蓖,就需要一個(gè)非圖片上下文曹鸠。對(duì)于上下文的理解,可以認(rèn)為就是一張畫布斥铺,然后在上面進(jìn)行繪圖操作彻桃。

context:圖形上下文,可以通過UIGraphicsGetCurrentContext:獲取當(dāng)前視圖的上下文

imageContext:圖片上下文晾蜘,可以通過UIGraphicsBeginImageContextWithOptions:獲取一個(gè)圖片上下文邻眷,然后繪制完成后,調(diào)用UIGraphicsGetImageFromCurrentImageContext獲取繪制的圖片剔交,最后要記得關(guān)閉圖片上下文UIGraphicsEndImageContext肆饶。

4.CGPathRef / UIBezierPath

圖形的繪制需要繪制一個(gè)路徑,然后再把路徑渲染出來岖常,而CGPathRef就是CoreGraphics框架中的路徑繪制類驯镊,UIBezierPath是封裝CGPathRef的面向OC的類,使用更加方便竭鞍,但是一些高級(jí)特性還是不及CGPathRef阿宅。

四、具體繪圖方法

由于iOS常用的繪圖框架有UIKitCoreGraphics兩個(gè)笼蛛,所以繪圖的方法也有多種,下面介紹一下iOS的幾種常用的繪圖方法蛉鹿。

1.圖片類型的上下文

圖片上下文的繪制不需要在drawRect:方法中進(jìn)行滨砍,在一個(gè)普通的OC方法中就可以繪制

使用UIKit實(shí)現(xiàn)

// 獲取圖片上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
// 繪圖
UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
[[UIColor blueColor] setFill];
[p fill];
// 從圖片上下文中獲取繪制的圖片
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
// 關(guān)閉圖片上下文
UIGraphicsEndImageContext();

使用CoreGraphics實(shí)現(xiàn)

// 獲取圖片上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
// 繪圖
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
CGContextFillPath(con);
// 從圖片上下文中獲取繪制的圖片
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
// 關(guān)閉圖片上下文
UIGraphicsEndImageContext();

2.drawRect:

UIView子類的drawRect:方法中實(shí)現(xiàn)圖形重新繪制,繪圖步驟如下:

  • 獲取上下文
  • 繪制圖形
  • 渲染圖形

UIKit方法

- (void) drawRect: (CGRect) rect {
    UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
    [[UIColor blueColor] setFill];
    [p fill];
}

CoreGraphics

- (void) drawRect: (CGRect) rect {
    CGContextRef con = UIGraphicsGetCurrentContext();
    CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
    CGContextFillPath(con);
}

3.drawLayer:inContext:

UIView子類的drawLayer:inContext:方法中也可以實(shí)現(xiàn)繪圖任務(wù)妖异,它是一個(gè)圖層的代理方法惋戏,而為了能夠調(diào)用該方法,需要給圖層的delegate設(shè)置代理對(duì)象他膳,其中代理對(duì)象不能是UIView對(duì)象响逢,因?yàn)?code>UIView對(duì)象已經(jīng)是它內(nèi)部根層(隱式層)的代理對(duì)象,再將它設(shè)置為另一個(gè)層的代理對(duì)象就會(huì)出問題棕孙。

一個(gè)view被添加到其它view上時(shí)舔亭,圖層的變化如下:

  • 先隱式地把此viewlayerCALayerDelegate設(shè)置成此view
  • 調(diào)用此viewself.layerdrawInContext方法
  • 由于drawLayer方法的注釋:If defined, called by the default implementation of -drawInContext:說明了drawInContextif([self.delegate responseToSelector:@selector(drawLayer:inContext:)])就執(zhí)行drawLayer:inContext:方法,這里我們因?yàn)閷?shí)現(xiàn)了drawLayer:inContext:所以會(huì)執(zhí)行
  • [super drawLayer:layer inContext:ctx]會(huì)讓系統(tǒng)自動(dòng)調(diào)用此viewdrawRect:方法蟀俊,至此self.layer畫出來了
  • self.layer上再加一個(gè)子layer钦铺,當(dāng)調(diào)用[layer setNeedsDisplay];時(shí)會(huì)自動(dòng)調(diào)用此layerdrawInContext方法
  • 如果drawRect不重寫,就不會(huì)調(diào)用其layerdrawInContext方法肢预,也就不會(huì)調(diào)用drawLayer:inContext方法

調(diào)用內(nèi)部根層的drawLayer:inContext:

//如果drawRect不重寫矛洞,就不會(huì)調(diào)用其layer的drawInContext方法,也就不會(huì)調(diào)用drawLayer:inContext方法
-(void)drawRect:(CGRect)rect{
    NSLog(@"2-drawRect:");
    NSLog(@"drawRect里的CGContext:%@",UIGraphicsGetCurrentContext());
    //得到的當(dāng)前圖形上下文正是drawLayer中傳遞過來的
    [super drawRect:rect];
}

#pragma mark - CALayerDelegate
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    
    NSLog(@"1-drawLayer:inContext:");
    NSLog(@"drawLayer里的CGContext:%@",ctx);
    // 如果去掉此句就不會(huì)執(zhí)行drawRect!!!!!!!!
   [super drawLayer:layer inContext:ctx];
}

調(diào)用外部代理對(duì)象的drawLayer:inContext:

由于不能把UIView對(duì)象設(shè)置為CALayerDelegate的代理烫映,所以我們需要?jiǎng)?chuàng)建一個(gè)NSObject對(duì)象沼本,然后實(shí)現(xiàn)drawLayer:inContext:方法噩峦,這樣就可以在代理對(duì)象里繪制所需圖形。另外抽兆,在設(shè)置代理時(shí)识补,不需要遵守CALayerDelegate的代理協(xié)議,即這個(gè)方法是NSObject的郊丛,不需要顯式地指定協(xié)議李请。

// MyLayerDelegate.m
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
    CGContextAddEllipseInRect(ctx, CGRectMake(100,100,100,100));
    CGContextSetFillColorWithColor(ctx, [UIColor blueColor].CGColor);
    CGContextFillPath(ctx);
}

// ViewController.m

@interface ViewController () <CALayerDelegate>

@property (nonatomic, strong) id myLayerDelegate;

@end

@implementation ViewController

- (void)viewDidLoad {
    // 設(shè)置layer的delegate為NSObject子類對(duì)象
    _myLayerDelegate = [[MyLayerDelegate alloc] init];
    
    MyView *myView = [[MyView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:myView];
    CALayer *layer = [CALayer layer];
    layer.backgroundColor = [UIColor magentaColor].CGColor;
    layer.bounds = CGRectMake(0, 0, 300, 500);
    layer.anchorPoint = CGPointZero;
    layer.delegate = _myLayerDelegate;
    [layer setNeedsDisplay];
    [myView.layer addSublayer:layer];
}

詳細(xì)實(shí)現(xiàn)過程

當(dāng)UIView需要顯示時(shí),它內(nèi)部的層會(huì)準(zhǔn)備好一個(gè)CGContextRef(圖形上下文)厉熟,然后調(diào)用delegate(這里就是UIView)的drawLayer:inContext:方法导盅,并且傳入已經(jīng)準(zhǔn)備好的CGContextRef對(duì)象。而UIViewdrawLayer:inContext:方法中又會(huì)調(diào)用自己的drawRect:方法揍瑟。平時(shí)在drawRect:中通過UIGraphicsGetCurrentContext()獲取的就是由層傳入的CGContextRef對(duì)象白翻,在drawRect:中完成的所有繪圖都會(huì)填入層的CGContextRef中,然后被拷貝至屏幕绢片。


iOS繪圖框架分析如上滤馍,如有不足之處,歡迎指出底循,共同進(jìn)步巢株。(本文圖片來自互聯(lián)網(wǎng),版權(quán)歸原作者所有)

參考資料

iOS繪圖系統(tǒng)(一) UIKit與CoreGraphics

CoreGraphics之CGContextSaveGState與UIGraphicsPushContext

Core Graphics快速入門——從一行代碼說起

iOS繪圖教程

UIGraphicsPushContext

Basic Zooming Using the Pinch Gestures

iOS開發(fā)UI篇—CAlayer(自定義layer)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末熙涤,一起剝皮案震驚了整個(gè)濱河市阁苞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祠挫,老刑警劉巖那槽,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異等舔,居然都是意外死亡骚灸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門慌植,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甚牲,“玉大人,你說我怎么就攤上這事涤浇”钆海” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵只锭,是天一觀的道長(zhǎng)著恩。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么喉誊? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任邀摆,我火速辦了婚禮,結(jié)果婚禮上伍茄,老公的妹妹穿的比我還像新娘栋盹。我一直安慰自己,他們只是感情好敷矫,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布例获。 她就那樣靜靜地躺著,像睡著了一般曹仗。 火紅的嫁衣襯著肌膚如雪榨汤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天怎茫,我揣著相機(jī)與錄音收壕,去河邊找鬼。 笑死轨蛤,一個(gè)胖子當(dāng)著我的面吹牛蜜宪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播祥山,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼圃验,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了缝呕?” 一聲冷哼從身側(cè)響起损谦,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岳颇,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颅湘,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡话侧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闯参。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞻鹏。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鹿寨,靈堂內(nèi)的尸體忽然破棺而出新博,到底是詐尸還是另有隱情,我是刑警寧澤脚草,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布赫悄,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏埂淮。R本人自食惡果不足惜姑隅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望倔撞。 院中可真熱鬧讲仰,春花似錦、人聲如沸痪蝇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躏啰。三九已至趁矾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丙唧,已是汗流浹背愈魏。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留想际,地道東北人培漏。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像胡本,于是被迫代替她去往敵國(guó)和親牌柄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

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

  • 轉(zhuǎn)載自http://blog.csdn.net/KelvinFlying/article/details/7692...
    Anchoriter閱讀 1,005評(píng)論 1 52
  • 由于CoreGraphics框架有太多的API侧甫,對(duì)于初次接觸或者對(duì)該框架不是十分了解的人珊佣,在繪圖時(shí),對(duì)API的選擇...
    Daimer閱讀 378評(píng)論 0 1
  • 之前在涉及到繪圖時(shí)披粟,總是會(huì)有些迷茫咒锻,是時(shí)候了解一下繪圖原理了。 框架介紹 iOS的繪圖框架有多種守屉,我們平常最常用的...
    李華光閱讀 918評(píng)論 0 1
  • 對(duì)于一個(gè)信號(hào)惑艇,要通過數(shù)字通信系統(tǒng)進(jìn)行傳輸,首先是經(jīng)過采樣量化編碼拇泛,形成01的數(shù)字流滨巴,其后要經(jīng)過一系列的數(shù)字處理過程...
    橘子布谷閱讀 1,095評(píng)論 0 0
  • 我原來上過學(xué),我看到了像我這個(gè)黃色的和紅色的法拉利汽車俺叭,我的車就閉上眼睛了恭取,再也不說話了。 中午我們睡覺了熄守,睡醒了...
    豪達(dá)兄弟閱讀 305評(píng)論 0 0