iOS繪圖框架CoreGraphics

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

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

iOS的繪圖框架有多種,我們平常最常用的就是UIKit,其底層是依賴CoreGraphics實現(xiàn)的,而且絕大多數(shù)的圖形界面也都是由UIKit完成,并且UIImage、NSStringUIBezierPath、UIColor等都知道如何繪制自己,也提供了一些方法來滿足我們常用的繪圖需求。除了UIKit,還有CoreGraphicsCore Animation,Core Image,OpenGL ES等多種框架,來滿足不同的繪圖要求。各個框架的大概介紹如下:

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

繪圖系統(tǒng)

[圖片上傳失敗...(image-d5ea6-1524385468723)]

二、繪圖方式

實際的繪圖包括兩部分:視圖繪制視圖布局,它們實現(xiàn)的功能是不同的,在理解這兩個概念之前,需要了解一下什么是繪圖周期,因為都是在繪圖周期中進行繪制的。

繪圖周期

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

1.視圖繪制

調(diào)用UIViewdrawRect:方法進行繪制。如果調(diào)用一個視圖的setNeedsDisplay方法,那么該視圖就被標記為重新繪制,并且會在下一次繪制周期中重新繪制,自動調(diào)用drawRect:方法。

2.視圖布局

調(diào)用UIViewlayoutSubviews方法。如果調(diào)用一個視圖的setNeedsLayout方法,那么該視圖就被標記為需要重新布局,UIKit會自動調(diào)用layoutSubviews方法及其子視圖的layoutSubviews方法。

在繪圖時,我們應(yīng)該盡量多使用布局,少使用繪制,是因為布局使用的是GPU,而繪制使用的是CPU。GPU對于圖形處理有優(yōu)勢,而CPU要處理的事情較多,且不擅長處理圖形,所以盡量使用GPU來處理圖形。

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

iOS的繪圖有多種對應(yīng)的狀態(tài)切換,比如:pop/pushsave/restore、context/imageContextCGPathRef/UIBezierPath等,下面分別進行介紹:

1.pop / push

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

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

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 壓棧當前的繪圖狀態(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的繪圖必須在一個上下文中繪制,所以在繪圖之前要獲取一個上下文。如果是繪制圖片,就需要獲取一個圖片的上下文;如果是繪制其它視圖,就需要一個非圖片上下文。對于上下文的理解,可以認為就是一張畫布,然后在上面進行繪圖操作。

context:圖形上下文,可以通過UIGraphicsGetCurrentContext:獲取當前視圖的上下文

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

4.CGPathRef / UIBezierPath

圖形的繪制需要繪制一個路徑,然后再把路徑渲染出來,而CGPathRef就是CoreGraphics框架中的路徑繪制類,UIBezierPath是封裝CGPathRef的面向OC的類,使用更加方便,但是一些高級特性還是不及CGPathRef。

四、具體繪圖方法

由于iOS常用的繪圖框架有UIKitCoreGraphics兩個,所以繪圖的方法也有多種,下面介紹一下iOS的幾種常用的繪圖方法。

1.圖片類型的上下文

圖片上下文的繪制不需要在drawRect:方法中進行,在一個普通的OC方法中就可以繪制

使用UIKit實現(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實現(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:方法中實現(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:方法中也可以實現(xiàn)繪圖任務(wù),它是一個圖層的代理方法,而為了能夠調(diào)用該方法,需要給圖層的delegate設(shè)置代理對象,其中代理對象不能是UIView對象,因為UIView對象已經(jīng)是它內(nèi)部根層(隱式層)的代理對象,再將它設(shè)置為另一個層的代理對象就會出問題。

一個view被添加到其它view上時,圖層的變化如下:

  • 先隱式地把此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:方法,這里我們因為實現(xiàn)了drawLayer:inContext:所以會執(zhí)行
  • [super drawLayer:layer inContext:ctx]會讓系統(tǒng)自動調(diào)用此viewdrawRect:方法,至此self.layer畫出來了
  • self.layer上再加一個子layer,當調(diào)用[layer setNeedsDisplay];時會自動調(diào)用此layerdrawInContext方法
  • 如果drawRect不重寫,就不會調(diào)用其layerdrawInContext方法,也就不會調(diào)用drawLayer:inContext方法

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

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

#pragma mark - CALayerDelegate
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{

    NSLog(@"1-drawLayer:inContext:");
    NSLog(@"drawLayer里的CGContext:%@",ctx);
    // 如果去掉此句就不會執(zhí)行drawRect!!!!!!!!
   [super drawLayer:layer inContext:ctx];
}

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

由于不能把UIView對象設(shè)置為CALayerDelegate的代理,所以我們需要創(chuàng)建一個NSObject對象,然后實現(xiàn)drawLayer:inContext:方法,這樣就可以在代理對象里繪制所需圖形。另外,在設(shè)置代理時,不需要遵守CALayerDelegate的代理協(xié)議,即這個方法是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子類對象
    _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];
}

詳細實現(xiàn)過程

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


iOS繪圖框架分析如上,如有不足之處,歡迎指出,共同進步。(本文圖片來自互聯(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)

作者:飛魚灣
鏈接:http://www.itdecent.cn/p/b056e8c9142b
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載自http://blog.csdn.net/KelvinFlying/article/details/7692...
    Anchoriter閱讀 1,117評論 1 52
  • 由于CoreGraphics框架有太多的API,對于初次接觸或者對該框架不是十分了解的人,在繪圖時,對API的選擇...
    飛魚灣閱讀 1,158評論 0 7
  • 由于CoreGraphics框架有太多的API,對于初次接觸或者對該框架不是十分了解的人,在繪圖時,對API的選擇...
    Daimer閱讀 417評論 0 1
  • Core Graphics Framework是一套基于C的API框架,使用了Quartz作為繪圖引擎。它提供了低...
    ShanJiJi閱讀 1,736評論 0 20
  • 又是一年中秋節(jié)! 不是對于節(jié)日沒有感覺,只是節(jié)日對于一個天天都能回家,天天能夠與家人共進晚餐的人實在是意義...
    顧言君心閱讀 305評論 0 1

友情鏈接更多精彩內(nèi)容