iOS中的繪圖教程品讀后的頓悟篇(二)

UIKit框架Core Graphics

作為初學者,很容易被UIKit和Core Graphics兩個支持繪圖的框架迷惑。

UIKit

像UIImage、NSString(繪制文本)、UIBezierPath(繪制形狀)、UIColor都知道如何繪制自己。這些類提供了功能有限但使用方便的方法來讓我們完成繪圖任務。一般情況下,UIKit就是我們所需要的。

使用UiKit,你只能在當前上下文中繪圖,所以如果你當前處于UIGraphicsBeginImageContextWithOptions函數或drawRect:方法中,你就可以直接使用UIKit提供的方法進行繪圖。如果你持有一個context:參數,那么使用UIKit提供的方法之前,必須將該上下文參數轉化為當前上下文。幸運的是,調用UIGraphicsPushContext 函數可以方便的將context:參數轉化為當前上下文,記住最后別忘了調用UIGraphicsPopContext函數恢復上下文環境。

從第一段話開始就重要了,這給我很大啟示,UIKit框架中都是像UIImage、NSString(繪制文本)、UIBezierPath(繪制形狀)、UIColor,這些類的調用方法,我就想起在上一篇中我們發現CoreGraphics是基于C的一套API.這樣我們就可以大致區分起來.用UI,NS 開頭都這些類.調用的方法就屬于UIKIt框架.

第二段話就可以和我們上一篇最后一個問題對上了,這個Context參數到底怎么用?

調用UIGraphicsPushContext 函數可以方便的將context:參數轉化為當前上下文,記住最后別忘了調用UIGraphicsPopContext函數恢復上下文環境。

代碼具體怎么寫? 我們接著品讀....

Core Graphics

這是一個繪圖專用的API族,它經常被稱為QuartZ或QuartZ 2D。Core Graphics是iOS上所有繪圖功能的基石,包括UIKit。

使用Core Graphics之前需要指定一個用于繪圖的圖形上下文(CGContextRef),這個圖形上下文會在每個繪圖函數中都會被用到。如果你持有一個圖形上下文context:參數,那么你等同于有了一個圖形上下文,這個上下文也許就是你需要用來繪圖的那個。如果你當前處于UIGraphicsBeginImageContextWithOptions函數或drawRect:方法中,并沒有引用一個上下文。為了使用Core Graphics,你可以調用UIGraphicsGetCurrentContext函數獲得當前的圖形上下文。

至此,我們有了兩大繪圖框架的支持以及三種獲得圖形上下文的方法(drawRect:、drawRect: inContext:、UIGraphicsBeginImageContextWithOptions)。那么我們就有6種繪圖的形式。如果你有些困惑了,不用怕,我接下來將說明這6種情況。無需擔心還沒有具體的繪圖命令,你只需關注上下文如何被創建以及我們是在使用UIKit還是Core Graphics。

從這里感覺就沒什么說的,思緒捋通了,跟著教程一步一步來.也有可能有什么遺漏了,由于我目前的水平,無法看出什么門道.

下面繪圖方法的具體的代碼了:

第一種繪圖形式:在UIView的子類方法drawRect:中繪制一個藍色圓,使用UIKit在Cocoa為我們提供的當前上下文中完成繪圖任務。

- (void) drawRect: (CGRect) rect { 
UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; 
 [[UIColor blueColor] setFill]; 
 [p fill]; 
 } 

這個代碼要注意:
1.使用的cocoa提供給我們的上下文.
2.這里我們使用的是UIKit框架中兩個類UIBezierPath , UIColor
3.以及這兩個結構 組合的時候,沒有出現上下文的影子.但確實是有上下文存在.在回看本篇開頭對UIKIt框架繪圖APi的介紹.可以直接使用API進行繪制.可以理解這里對上下文進行了封裝.

第二種繪圖形式:使用Core Graphics實現繪制藍色圓。

- (void) drawRect: (CGRect) rect { 
  //這里我對代碼稍微注釋下:
CGContextRef con = UIGraphicsGetCurrentContext(); 

CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); 
 //設定規則 繪制上下文的形狀
CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); 
 //設定規則 繪制上下文的顏色
CGContextFillPath(con); 
//Paints the area within the current path, using the nonzero winding number rule.
//就是說使用前面設定的規則,來繪制當前path內的區域.
//Quartz treats each subpath as if it were closed by calling CGContextClosePath. The nonzero winding number rule is described in Filling a Path in Quartz 2D Programming Guide. 
As a side effect when you call this function, Quartz clears the current path.
//一般用的是CGContextClosePath 這個方法,現在這個代碼會有一個副作用,會清除當前的路徑.大家使用的時候要注意.看各種情況下使用吧.
} 

第二種和第一種對比著看,就很清晰了.這個使用的是Core Graphics框架繪圖.這時候我們再回頭看本篇中Core Graphics中介紹,

使用Core Graphics之前需要指定一個用于繪圖的圖形上下文(CGContextRef)

所以,我就懂了,繪制的時候第一步要先獲取上下文的應用.
說下,上面兩個方法的效果是一樣的,都是一個藍色的填充的圓.

第三種繪圖形式:我將在UIView子類的drawLayer:inContext:方法中實現繪圖任務。drawLayer:inContext:方法是一個繪制圖層內容的代理方法。為了能夠調用drawLayer:inContext:方法,我們需要設定圖層的代理對象。但要注意,不應該將UIView對象設置為顯示層的委托對象,這是因為UIView對象已經是隱式層的代理對象,再將它設置為另一個層的委托對象就會出問題。輕量級的做法是:編寫負責繪圖形的代理類。在MyView.h文件中聲明如下代碼:

@interface MyLayerDelegate : NSObject 

@end 

然后MyView.m文件中實現接口代碼:

@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 

直接將代理類的實現代碼放在MyView.m文件的#import代碼的下面,這樣感覺好像在使用私有類完成繪圖任務(雖然這不是私有類)。需要注意的是,我們所引用的上下文并不是當前上下文,所以為了能夠使用UIKit,我們需要將引用的上下文轉變成當前上下文。
因為圖層的代理是assign內存管理策略,那么這里就不能以局部變量的形式創建MyLayerDelegate實例對象賦值給圖層代理。這里選擇在MyView.m中增加一個實例變量,因為實例變量默認是strong:

@interface MyView () { 

MyLayerDelegate* _layerDeleagete; 

} 

@end 

使用該圖層代理:

MyView *myView = [[MyView alloc] initWithFrame: CGRectMake(0, 0, 320, 480)]; 

CALayer *myLayer = [CALayer layer]; 

_layerDelegate = [[MyLayerDelegate alloc] init]; 

myLayer.delegate = _layerDelegate; 

[myView.layer addSublayer:myLayer]; 

[myView setNeedsDisplay]; // 調用此方法,drawLayer: inContext:方法才會被調用。 

為了保證教程的完整性,我把三種方法的代碼和說明一起復制過來,來說明.第三種方法看似這么復雜,如果僅說明繪制這部分.
我來說簡單點的步驟:

第一步.在我們自定義的UIVIew----MyDrawView中重寫

-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
UIGraphicsPushContext(ctx);

UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];

[[UIColor blueColor] setFill];

[p fill];

UIGraphicsPopContext();

}

第二步.在ViewController中使用

- (void)viewDidLoad {
[super viewDidLoad];

MyDrawView *mydrayView = [[MyDrawView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
[self.view addSubview:mydrayView];
[mydrayView.layer setNeedsDisplay];

// Do any additional setup after loading the view, typically from a nib.
}

然后品讀這段教程中的代碼:
大致是封裝一個MyView的自定義View類.
新建了一個圖層,添加在視圖上,并為這個圖層設置了一個代理.并重寫了繪制圖層的方法.這里重寫的方法的代碼也就回答了,context引用轉換成真正的畫布的context上下文.如何寫代碼的問題了.
這里需要注意的是:
1.要有圖層的概念.在view調用[myView setNeedsDisplay]這個的時候,新加的圖層也會被重繪,并調用我們重寫的代理方法.
2.我們在interface 中增加一個實例變量的默認屬性是Strong.

第四種繪圖形式: 使用Core Graphics在drawLayer:inContext:方法中實現同樣操作,代碼如下:

- (void)drawLayer:(CALayer*)lay inContext:(CGContextRef)con { 

CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100)); 

CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); 

CGContextFillPath(con); 

} 

這個我感覺沒什么說的,替換了UIKit繪圖的框架,類比第二種方法.

第五種繪圖形式: 使用UIKit實現:
最后,演示UIGraphicsBeginImageContextWithOptions的用法,并從上下文中生成一個UIImage對象。生成UIImage對象的代碼并不需要等待某些方法被調用后或在UIView的子類中才能去做。

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0); 

UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)]; 

[[UIColor blueColor] setFill]; 

[p fill]; 

UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 

UIGraphicsEndImageContext(); 

解釋一下UIGraphicsBeginImageContextWithOptions函數參數的含義:第一個參數表示所要創建的圖片的尺寸;第二個參數用來指定所生成圖片的背景是否為不透明,如上我們使用YES而不是NO,則我們得到的圖片背景將會是黑色,顯然這不是我想要的;第三個參數指定生成圖片的縮放因子,這個縮放因子與UIImage的scale屬性所指的含義是一致的。傳入0則表示讓圖片的縮放因子根據屏幕的分辨率而變化,所以我們得到的圖片不管是在單分辨率還是視網膜屏上看起來都會很好。

這段話,作者自己有解釋.我們要注意的是
UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); 這行代碼是從當前上下文中回去圖片,我們是否可以在其他地方例如cocoa提供的上下文中使用呢?

答案是否定的.看方法注釋
You should call this function only when a bitmap-based graphics context is the current graphics context. If the current context is nil or was not created by a call to UIGraphicsBeginImageContext, this function returns nil.
前一句只有是位圖圖形的上下文才可以.我還不是很懂,但是下面一句,如果這個上下文,不是用UIGraphicsBeginImageContext 創建的,那么這個方法就會返回nil,所以這個方法使用有限定,大家要注意了.

第六種繪圖形式: 使用Core Graphics實現:

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(); 

UIKit和Core Graphics可以在相同的圖形上下文中混合使用。在iOS 4.0之前,使用UIKit和UIGraphicsGetCurrentContext被認為是線程不安全的。而在iOS4.0以后蘋果讓繪圖操作在第二個線程中執行解決了此問題。

第六種繪圖方法也沒有什么說的,就是下面這種說兩個框架可以混合使用,這種操作一般是沒有什么問題,從我的感覺上來說,要不斷地進行縣城交叉渲染的時候盡量避免雙重框架混合使用.避免不必要的bug.也就是沒事別惹事.有事先看這里.出bug,看看是不是混合使用的問題.

下面的教程就是一些使用的例子.
等下篇,我再一一品讀.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容

  • Core Graphics Framework是一套基于C的API框架,使用了Quartz作為繪圖引擎。它提供了低...
    ShanJiJi閱讀 1,552評論 0 20
  • 原文地址:http://www.cocoachina.com/industry/20140115/7703.htm...
    默默_David閱讀 6,130評論 0 1
  • --繪圖與濾鏡全面解析 概述 在iOS中可以很容易的開發出絢麗的界面效果,一方面得益于成功系統的設計,另一方面得益...
    韓七夏閱讀 2,756評論 2 10
  • 微信公眾號支付分為三類 以下主要介紹公眾號H5授權支付 首先看一下微信支付的業務邏輯 大部分微信支付邏輯在于服務器...
    撩人C小羅閱讀 2,694評論 0 0
  • 感恩每天的生活,使我越來越成熟!
    寶貴的愛閱讀 247評論 0 0