介紹
說到iOS的繪圖肯定就是Core Graphics。
Core Graphics Framework是一套基于C的API框架,使用了Quartz作為繪圖引擎。它提供了低級別、輕量級、高保真度的2D渲染。該框架可以用于基于路徑的繪圖、變換、顏色管理、脫屏渲染,模板、漸變、遮蔽、圖像數據管理、圖像的創建、遮罩以及PDF文檔的創建、顯示和分析。
獲取圖形上下文
Core Graphics API所有的操作都在一個上下文中進行。所以在繪圖之前需要獲取該上下文并傳入執行渲染的函數中。如果你正在渲染一副在內存中的圖片,此時就需要傳入圖片所屬的上下文。獲得一個圖形上下文是我們完成繪圖任務的第一步,你可以將圖形上下文理解為一塊畫布。如果你沒有得到這塊畫布,那么你就無法完成任何繪圖操作。
獲取圖形上下文的幾種方式:
1.drawRect:
2.inContext: (-(void)drawInContext:(CGContextRef)ctx - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx)
3.UIGraphicsBeginImageContextWithOptions
兩大繪圖框架:
UIKit
像UIImage、NSString(繪制文本)、UIBezierPath(繪制形狀)、UIColor都知道如何繪制自己。
這些類提供了功能有限但使用方便的方法來讓我們完成繪圖任務。一般情況下,UIKit就是我們所需要的。
Core Graphics
這是一個繪圖專用的API族,它經常被稱為QuartZ或QuartZ 2D。Core Graphics是iOS上所有繪圖
功能的基石,包括UIKit。
6種繪圖的形式
-
第一種UIKit框架drawRect:
在UIView的子類方法drawRect:中繪制一個藍色圓 - (void) drawRect: (CGRect) rect { UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; [[UIColor blueColor] setFill]; [p fill]; }
-
第二種Core Graphics框架inContext:
- (void)drawRect:(CGRect)rect{ //當前上下文及畫布為當前view CGContextRef con = UIGraphicsGetCurrentContext(); CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); CGContextFillPath(con); }
-
第三種UIKit框架inContext:
drawInContext:方法 @interface TestLayer : CALayer @end @implementation TestLayer - (void)drawInContext:(CGContextRef)ctx{ UIGraphicsPushContext(ctx); UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; [[UIColor blueColor] setFill]; [p fill]; UIGraphicsPopContext(); } @end @implementation XXXViewController{ - (void)viewDidLoad{ [super viewDidLoad]; //1.創建自定義的layer TestLayer *layer=[TestLayer layer]; //2.設置layer的屬性 layer.backgroundColor= [UIColor blackColor].CGColor; layer.frame=CGRectMake(100, 100, 200, 200); [layer setNeedsDisplay]; //3.添加layer [self.view.layer addSublayer:layer]; } @end drawLayer: inContext:方法 @interface MyLayerDelegate : NSObject @end @implementation MyLayerDelegate - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx { UIGraphicsPushContext(ctx); UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; [[UIColor blueColor] setFill]; [p fill]; UIGraphicsPopContext(); } @end @implementation XXXViewController{ MyLayerDelegate *_layerDeleagete; CALayer *_layer; } - (void)viewDidLoad{ [super viewDidLoad]; _layerDeleagete = [[MyLayerDelegate alloc] init]; //1.創建自定義的layer _layer=[CALayer layer]; //2.設置layer的屬性 _layer.backgroundColor= [UIColor blackColor].CGColor; _layer.frame=CGRectMake(100, 100, 200, 200); _layer.delegate = _layerDeleagete; [_layer setNeedsDisplay]; //3.添加layer [self.view.layer addSublayer:_layer]; } - (void)dealloc{ _layer.delegate = nil; } @end
-
第四種Core Graphics框架inContext:
drawInContext:方法 - (void)drawInContext:(CGContextRef)ctx{ CGContextAddEllipseInRect(ctx, CGRectMake(0,0,100,100)); CGContextSetFillColorWithColor(ctx, [UIColor blueColor].CGColor); CGContextFillPath(ctx); } drawLayer: inContext:方法 - (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx { UIGraphicsPushContext(ctx); UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; [[UIColor blueColor] setFill]; [p fill]; UIGraphicsPopContext(); }
-
第五種UIKit框架UIGraphicsBeginImageContextWithOptions
@implementation XXXViewController - (void)viewDidLoad{ [super viewDidLoad]; UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0); UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; [[UIColor blueColor] setFill]; [p fill]; UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); /*---------------------------------*/ UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; [imageView setImage:im]; [self.view addSubview:imageView]; } @end
-
第六種Core Graphics框架UIGraphicsBeginImageContextWithOptions
@implementation XXXViewController - (void)viewDidLoad{ [super viewDidLoad]; 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(); /*---------------------------------*/ UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; [imageView setImage:im]; [self.view addSubview:imageView]; }
注意
- 使用- (void)drawRect:(CGRect)rect需要注意的地方
以下方法調用drawRect
1.如果在UIView初始化時沒有設置rect大小,將直接導致drawRect不被自動調用。
2.該方法在調用sizeThatFits后被調用,所以可以先調用sizeToFit計算出size。然后系統自動調用drawRect:方法。
3.通過設置contentMode屬性值為UIViewContentModeRedraw。那么將在每次設置或更改frame的時候自動調用drawRect:。
4.直接調用setNeedsDisplay,或者setNeedsDisplayInRect:觸發drawRect:,但是有個前提條件是rect不能為0.
- 若要實時畫圖,不能使用gestureRecognizer,只能使用touchbegan等方法來掉用setNeedsDisplay實時刷新屏幕
問題
- 比如當在一個view上繪制一條線之類的,是直接用addsubView添加一個UIView控件好,還是在drawRect:里用繪圖代碼繪制一條線好?
哪種更高效,或者一樣?
文章內存惡鬼drawRect里有提到,使用drawRect方法被調用,它就會為視圖分配一個寄宿圖,這個寄宿圖的像素尺寸等于視圖大小乘以contentsScale
(這個屬性與屏幕分辨率有關,我們的畫板程序在不同模擬器下呈現的內存用量不同也是因為它)的值。
比如在iphone6上UIView的frame為
CGRectMake(0, 0, SCREEN_SIZE.width*5, SCREEN_SIZE.height*5)則內存增加為750*5*1334*5*4/1024/2024,相當于95.4M內存
所以當使用drawRect:時,即使里面沒寫任何繪制代碼,也會分配一個寄宿圖,內存也會對應的增加。
文中提到一旦你實現了CALayerDelegate協議中的-drawLayer:inContext:
方法或者UIView中的-drawRect:方法(其實就是前者的包裝方法),圖層就創建了一個繪制上下文。內存也會暴增。
但是使用我上面inContext:方法獲取上下文,好像是不會產生寄宿圖的,內存也不會增加的那么夸張。測試例子
咨詢了原文作者,他說其實在inContext:調用之前會創建一個合適大小的寄宿圖,這個合適大小實際上是由系統控制的,它有一個依據是繪制內容,所以繪制內容越大內存也會越大的。還有layer的frame變大也會增大內存,然后layer的frame增大到某一個限度之后,frame再增大內存也不會增加了。所以整個增加的內存其實是受到多個方面的影響的,只是這個增加的內存不知道怎么算了?
所以能不用drawRect:就盡量不用吧
源碼,應用場景
以上六種方式繪制圓的代碼
繪圖代碼比較常用就是圖表和繪畫板這兩種場景。
兩個可以學習的源碼:
圖表:BEMSimpleLineGraph
繪畫板:Brushes