iOS繪圖

介紹

說到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實時刷新屏幕

問題

  1. 比如當在一個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

參考####

iOS繪圖教程

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容