iOS是怎么"繪畫"的?


什么是繪圖引擎

如果您以前從事其它平台的圖形/界面開發或者游戲開發,一定知道, 不管上層UI怎么呈現和響應, 底層必須有一個繪圖引擎. iOS也不例外. 本文詳細介紹了iOS Graphics的用法和相關知識, 希望對您的Coding有幫助.


  • 博客: http://www.cnblogs.com/jhzhu
  • 郵箱: jhzhuustc@gmail.com
  • 作者: 知明所以
  • 時間: 2013-12-31

^此博客需要對CALayerUIView有基本的了解. 可參考博客談談iOS Animation

什么是繪圖引擎

繪圖引擎, 通俗來說就好比給你一張紙一支筆和若干顏色的顏料, 你可以用它來做最基本的圖形繪制.
一個最基本的繪圖引擎包括一下接口:

//Code-1
I1. drawLine()      //繪制任意線條,並支持對線條上色.
I2. drawPath()      //根據路徑繪制形狀,並支持填充顏色.
I3. drawImage()     //繪制圖像(e.g. xxx.jpg, xxx.png)
I4. drawGradient()  //繪制漸變填充
I5. transform()     //矩陣映射變換.
I6. drawText()      //繪制文字

不難想象, 有了以上接口, 我們就可以方便的繪制任意想要的圖像.
這里強調的是方便, 有些接口並不是必須的. 比如說drawImage(),我們總可以調用有限次drawLine()drawShape()來繪制任意給定的Image. 但是復雜程度可想而知.
一個繪圖引擎設計的目的就是為了方便上層調用, 所以它會封裝一些最常用最基本的接口. 以上5個接口就滿足這兩個條件之一. 所謂最常用最基本並沒有一個明確的定義, 所以不同的繪圖引擎可能會多一些常用接口,但都大同小異.



iOS的繪圖引擎

下面我們就Code-1里提到的接口在iOS平台上做一個介紹.

在哪里繪制?

如果我們在XCode里新建一個UIView類, 我們會得到以下代碼:

//Code-2
#import "GraphicsViewControllerView.h"
@implementation GraphicsViewControllerView
- (id)initWithFrame:(CGRect)frame
{ 
    self = [super initWithFrame:frame]; 
    if (self) {
        // Initialization code }
    return self; 
}
- (void)drawRect:(CGRect)rect
{
    // Drawing code 
}
@end

通常,drawRect()都會被注釋起來. 因為, 如果你向UIView添加subView或者設置UIView的顯示相關的屬性(e.g. backgroundCrolor)的時候, UIKit會自動的把這些參數代表的含義繪制到CALayer上去. 也就是說, 一般情況我們並不需要自己來繪制, UIKit會自動幫我們完成繪制工作.
但是, 當不添加subView, 不設置UIView的顯示相關的屬性時, 我們就可以通過重載drawRect()來手動繪制圖像了.

Context

A graphical context can be thought of as a canvas, offering an enormous number of properties such as pen color, pen thickness, etc. Given the context, you can start painting straight away inside the drawRect: method, and Cocoa Touch will make sure that the attributes and properties of the context are applied to your drawings. We will talk about this more later, but now, let’s move on to more interesting subjects.

drawText

我們新建一個UIViewCustomUIView,如下重載drawRect()方法.
新建CustomUIView的對象,不設置任何屬性,添加到顯示列表.

//Code-3
- (void)drawRect:(CGRect)rect{ 
    UIFont *font = [UIFont systemFontWithSize:40.f]; 
    NSString *myString = @"Some String";
    [myString drawAtPoint:CGPointMake(40, 180) withFont:font];
}

運行, 就可以看到我們沒有添加任何UITextField卻顯示了文字~

more:

關於文字繪制的方法還有 drawInRect:withFont:等幾個方法, 可參考官方文檔.

setColor

我們把Code-3中的代碼添加兩行, 變成:

//Code-4
- (void)drawRect:(CGRect)rect{ 
    UIColor* color = [UIColor blueColor];       //create color
    [color set];                                //set color

    UIFont *font = [UIFont systemFontWithSize:40.f]; 
    NSString *myString = @"Some String";
    [myString drawAtPoint:CGPointMake(40, 180) withFont:font];
}

就可以看到,文字由黑色變成了藍色.

more:

UIColor還有兩個方法setStrokesetFill分別設置線條顏色和填充顏色. 在后面的章節會用到.set方法影響所有前景色.

drawImage

同樣的, 如下重寫drawRect方法:

//Code-5
- (void)drawRect:(CGRect)rect{ 
    /* Assuming the image is in your app bundle and we can load it */
    UIImage *xcodeIcon = [UIImage imageNamed:@"filename.png"];
    [xcodeIcon drawAtPoint:CGPointMake(0.0f, 20.0f)];
}

我們看到圖像被繪制出來了.

more:

UIImage還有其他繪制方法:

drawInRect:
drawAsPatternInRect:
drawAtPoint:blendMode:alpha:
drawInRect:blendMode:alpha:

方法名已經很清楚的說明了方法的用途. 具體可參考官方文檔

drawLine

這兩個是繪圖引擎里最基本的, 所以放在一起講述.

//Code-6: drawLine
- (void)drawRect:(CGRect)rect{ 

    /* Step1 設置繪圖顏色 */ 
    [[UIColor brownColor] set];

    /* Step2 獲取當期的畫布: Graphic Context */ 
    CGContextRef currentContext = UIGraphicsGetCurrentContext();

    /* Step3 設置線條寬度 */ 
    CGContextSetLineWidth(currentContext,5.0f);

    /* Step4 把畫筆移動到起始點 */ 
    CGContextMoveToPoint(currentContext,50.0f, 10.0f);

    /* Step5 從起始點繪制線條到終點 */ 
    CGContextAddLineToPoint(currentContext,100.0f, 200.0f);

    /* Step6 提交繪制 */
    CGContextStrokePath(currentContext); 

}

如果想連續繪制多條線, 可以再Code-6中的Step5Step6之間多次調用CGContextAddLineToPoint().

more:

CGContextSetLineJoin可以改變線條交叉點的樣式.

drawPath

如果我們想快速繪制一條折線, 調用drawLine就顯得有些臃腫. 所以有了drawPath, 它是drawLine的加強版.
drawPath的一般步驟如下:

//Code-7: drawPath
- (void)drawRect:(CGRect)rect{ 

    /* Step1 獲取當期的畫布: Graphic Context */ 
    CGContextRef currentContext = UIGraphicsGetCurrentContext();

    /* Step2 創建 path */ 
    CGMutablePathRef path = CGPathCreateMutable();

    /* Step3 移動到起始點 */
    CGPathMoveToPoint(path,NULL, screenBounds.size.width, screenBounds.origin.y);

    /* Step4 繪制一個橢圓 */
    CGPathAddEllipseInRect(path, &CGAffineTransformIdentity, CGRectMake(0, 320, 320, 160));

    /* Step5 再添加一條直線 */
    CGPathAddLineToPoint(path,NULL, screenBounds.origin.x, screenBounds.size.height);

    /* Step6 向畫布添加path */
    CGContextAddPath(currentcontext, path);

    /* Step7 設置繪制類型: kCGPathStroke(繪制邊緣), kCGPathFill(填充path內區域), kCGPathFillStroke(包含前面兩項)*/
    CGContextDrawPath(currentcontext, kCGPathFillStroke);

    /* Step8 提交繪制 */
    CGContextStrokePath(currentContext); 

    /* Step9 release path */
    CGPathRelease(path);

}

more:

常見幾何圖形的繪制接口: CGPathAddCurveToPoint,CGPathAddArcToPoint,CGPathAddRect等等...

transform

iOS中的transform使用CGAffineTransform表示的. 你可以用矩陣方式構造任意二維變換:

/*
a: The value at position [1,1] in the matrix.
b: The value at position [1,2] in the matrix.
c: The value at position [2,1] in the matrix.
d: The value at position [2,2] in the matrix.
tx: The value at position [3,1] in the matrix.
ty: The value at position [3,2] in the matrix.
*/
CGAffineTransformMake(CGFloat a, CGFloat b, CGFloat c, CGFloat d, CGFloat tx, CGFloat ty)

矩陣表示為:

iOS也提供了常見變換的快速構建方式:

CGAffineTransformIdentity           //單位矩陣, 不做任何變換
CGAffineTransformMakeRotation       //旋轉
CGAffineTransformMakeScale          //縮放
CGAffineTransformMakeTranslation    //平移

Code-7 Step4中, 繪制path時用到了單位矩陣CGAffineTransformIdentity, 表示不做任何變換. 通常情況下, 都是在繪制階段把transform作為參數傳入. 上面提到的CGPathAddCurveToPoint,CGPathAddArcToPoint,CGPathAddRect函數都有一個transform參數.



iOS繪圖在項目中的應用

通常, 我們只需要隨心所欲的對UIView增加subView, UIKit會自動幫我們繪制. 但是下列情況下可能需要手動繪制:

  1. 優化UITableViewCell的時候. 如果我們的cell很復雜, 有很多subView, 就會變得很卡頓. 就需要手動繪制了. 可參考博客: 優化UITableView性能或者IOS詳解TableView——性能優化及手工繪制UITableViewCell
  2. 暫未想到, 以后想到再說.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM