因爲CoreGraphics框架有太多的API,對於初次接觸或者對該框架不是十分了解的人,在繪圖時,對API的選擇會感到有些迷茫,甚至會以爲iOS的圖形繪製有些繁瑣。所以,本文主要介紹一下iOS的繪圖方法和分析一下CoreGraphics框架的繪圖原理。html
1、繪圖系統簡介ios
iOS的繪圖框架有多種,咱們日常最經常使用的就是UIKit,其底層是依賴CoreGraphics實現的,並且絕大多數的圖形界面也都是由UIKit完成,而且UIImage、NSString、UIBezierPath、UIColor等都知道如何繪製本身,也提供了一些方法來知足咱們經常使用的繪圖需求。除了UIKit,還有CoreGraphics、Core Animation,Core Image,OpenGL ES等多種框架,來知足不一樣的繪圖要求。各個框架的大概介紹以下:編程
UIKit:最經常使用的視圖框架,封裝度最高,都是OC對象框架
CoreGraphics:主要繪圖系統,經常使用於繪製自定義視圖,純C的API,使用Quartz2D作引擎佈局
CoreAnimation:提供強大的2D和3D動畫效果動畫
CoreImage:給圖片提供各類濾鏡處理,好比高斯模糊、銳化等atom
OpenGL-ES:主要用於遊戲繪製,但它是一套編程規範,具體由設備製造商實現spa
繪圖系統線程
2、繪圖方式代理
實際的繪圖包括兩部分:視圖繪製和視圖佈局,它們實現的功能是不一樣的,在理解這兩個概念以前,須要瞭解一下什麼是繪圖週期,由於都是在繪圖週期中進行繪製的。
繪圖週期:
iOS在運行循環中會整合全部的繪圖請求,並一次將它們繪製出來
不能在子線程中繪製,也不能進行復雜的操做,不然會形成主線程卡頓
1.視圖繪製
調用UIView的drawRect:方法進行繪製。若是調用一個視圖的setNeedsDisplay方法,那麼該視圖就被標記爲從新繪製,而且會在下一次繪製週期中從新繪製,自動調用drawRect:方法。
2.視圖佈局
調用UIView的layoutSubviews方法。若是調用一個視圖的setNeedsLayout方法,那麼該視圖就被標記爲須要從新佈局,UIKit會自動調用layoutSubviews方法及其子視圖的layoutSubviews方法。
在繪圖時,咱們應該儘可能多使用佈局,少使用繪製,是由於佈局使用的是GPU,而繪製使用的是CPU。GPU對於圖形處理有優點,而CPU要處理的事情較多,且不擅長處理圖形,因此儘可能使用GPU來處理圖形。
3、繪圖狀態切換
iOS的繪圖有多種對應的狀態切換,好比:pop/push、save/restore、context/imageContext和CGPathRef/UIBezierPath等,下面分別進行介紹:
1.pop / push
設置繪圖的上下文環境(context)
push:UIGraphicsPushContext(context)把context壓入棧中,並把context設置爲當前繪圖上下文
pop:UIGraphicsPopContext將棧頂的上下文彈出,恢復先前的上下文,可是繪圖狀態不變
下面繪製的視圖是黑色
- (void)drawRect:(CGRect)rect { [[UIColor redColor] setFill]; UIGraphicsPushContext(UIGraphicsGetCurrentContext()); [[UIColor blackColor] setFill]; UIGraphicsPopContext(); UIRectFill(CGRectMake(90, 340, 100, 100)); // black color }
2.save / restore
設置繪圖的狀態(state)
save:CGContextSaveGState 壓棧當前的繪圖狀態,僅僅是繪圖狀態,不是繪圖上下文
restore:恢復剛纔保存的繪圖狀態
下面繪製的視圖是紅色
- (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:獲取一個圖片上下文,而後繪製完成後,調用UIGraphicsGetImageFromCurrentImageContext獲取繪製的圖片,最後要記得關閉圖片上下文UIGraphicsEndImageContext。
4.CGPathRef / UIBezierPath
圖形的繪製須要繪製一個路徑,而後再把路徑渲染出來,而CGPathRef就是CoreGraphics框架中的路徑繪製類,UIBezierPath是封裝CGPathRef的面向OC的類,使用更加方便,可是一些高級特性仍是不及CGPathRef。
4、具體繪圖方法
因爲iOS經常使用的繪圖框架有UIKit和CoreGraphics兩個,因此繪圖的方法也有多種,下面介紹一下iOS的幾種經常使用的繪圖方法。
1.圖片類型的上下文
圖片上下文的繪製不須要在drawRect:方法中進行,在一個普通的OC方法中就能夠繪製
使用UIKit實現
// 獲取圖片上下文 UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0); // 繪圖 UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; [[UIColor blueColor] setFill]; [p fill]; // 從圖片上下文中獲取繪製的圖片 UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); // 關閉圖片上下文 UIGraphicsEndImageContext();
使用CoreGraphics實現
// 獲取圖片上下文 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(); // 關閉圖片上下文 UIGraphicsEndImageContext();
2.drawRect:
在UIView子類的drawRect:方法中實現圖形從新繪製,繪圖步驟以下:
獲取上下文
繪製圖形
渲染圖形
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:方法中也能夠實現繪圖任務,它是一個圖層的代理方法,而爲了可以調用該方法,須要給圖層的delegate設置代理對象,其中代理對象不能是UIView對象,由於UIView對象已是它內部根層(隱式層)的代理對象,再將它設置爲另外一個層的代理對象就會出問題。
一個view被添加到其它view上時,圖層的變化以下:
先隱式地把此view的layer的CALayerDelegate設置成此view
調用此view的self.layer的drawInContext方法
因爲drawLayer方法的註釋:If defined, called by the default implementation of -drawInContext:說明了drawInContext裏if([self.delegate responseToSelector:@selector(drawLayer:inContext:)])就執行drawLayer:inContext:方法,這裏咱們由於實現了drawLayer:inContext:因此會執行
[super drawLayer:layer inContext:ctx]會讓系統自動調用此view的drawRect:方法,至此self.layer畫出來了
在self.layer上再加一個子layer,當調用[layer setNeedsDisplay];時會自動調用此layer的drawInContext方法
若是drawRect不重寫,就不會調用其layer的drawInContext方法,也就不會調用drawLayer:inContext方法
調用內部根層的drawLayer:inContext:
//若是drawRect不重寫,就不會調用其layer的drawInContext方法,也就不會調用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); // 若是去掉此句就不會執行drawRect!!!!!!!! [super drawLayer:layer inContext:ctx]; }
調用外部代理對象的drawLayer:inContext:
因爲不能把UIView對象設置爲CALayerDelegate的代理,因此咱們須要建立一個NSObject對象,而後實現drawLayer:inContext:方法,這樣就能夠在代理對象裏繪製所需圖形。另外,在設置代理時,不須要遵照CALayerDelegate的代理協議,即這個方法是NSObject的,不須要顯式地指定協議。
// 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 () @property (nonatomic, strong) id myLayerDelegate; @end @implementation ViewController - (void)viewDidLoad { // 設置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]; }
詳細實現過程
當UIView須要顯示時,它內部的層會準備好一個CGContextRef(圖形上下文),而後調用delegate(這裏就是UIView)的drawLayer:inContext:方法,而且傳入已經準備好的CGContextRef對象。而UIView在drawLayer:inContext:方法中又會調用本身的drawRect:方法。平時在drawRect:中經過UIGraphicsGetCurrentContext()獲取的就是由層傳入的CGContextRef對象,在drawRect:中完成的全部繪圖都會填入層的CGContextRef中,而後被拷貝至屏幕。
iOS繪圖框架分析如上,若有不足之處,歡迎指出,共同進步。(本文圖片來自互聯網,版權歸原做者全部)
來源:http://www.cocoachina.com/ios/20170809/20187.html