iOS繪圖功能(一)

不同的繪圖系統(tǒng)###

iOS主要的繪圖系統(tǒng)有UIKit,Core Graphics(Quartz), Core Animation,Core Image和OpenGL ES,每一個都能針對不同的問題其作用。

UIKit 這是最高級的界面炉菲,是Objectiv-C中唯一的界面。它能用于輕松的訪問布局厚掷,組成熟呛,繪圖巴元,字體诅迷,圖片翁锡,動畫等桩了。可以通過UI前綴來識別UIKit元素埠戳,比如UIView和UIBezierPath井誉。UIKit也擴展NSString來利用方法(比如drawInRect:withFont:)簡化文本繪制。

Core Graphics (也稱Quartz 2D) UIkit下的主要繪圖系統(tǒng)乞而,頻繁用于繪制自定義視圖送悔。Core Graphics 是高度集成UIView和其他UIKit部分的慢显。Core Graphics 數(shù)據(jù)結(jié)構(gòu)和函數(shù)可以通過CG前綴來識別爪模。

Core Animation這個提供了強大的2D與3D動畫服務(wù)。它也與UIView高度集成荚藻。
Core Image最早在iOS5中出現(xiàn)的Mac技術(shù)屋灌。Core Image提供了非常快的圖片過濾方式应狱,比如切圖共郭,銳化,扭曲和其他你能想象的變形效果疾呻。

OpenGL ES 主要用來編寫高性能游戲(尤其是3D游戲)除嘹,OpenGL ES是OpenGL繪圖語言的子集。對于iOS的其他應(yīng)用來說岸蜗,Core Animation通常是更好的選擇尉咕。OpenGL ES在多個平臺可兼容。

UIKit和視圖繪制周期###

當(dāng)你要改變視圖的大小或者顯示璃岳,畫一條線年缎,或者改變一個對象的顏色,這些改變在屏幕上不會立即顯示出來铃慷。有事单芜,這回讓編寫如下不恰當(dāng)代碼的人感到迷惑:

progreessView.hidden = NO;
[self doSomethingTimeConsuming];
progressView.hidden = YES;

第一行(progressView.hidden = NO)實際上根本沒有作用犁柜,理解這一點很重要洲鸠。這個代碼不會使進度視圖在執(zhí)行耗時操作時顯示出來。無論這個方法運行了多久馋缅,你都不會看到視圖顯示出來扒腕。
所有的繪制都發(fā)生在主線程,只要代碼運行在主線程股囊,就沒有東西可以繪制了這就是不要子啊主線程中執(zhí)行長時間運行操作的一個原因袜匿。這不僅會阻礙繪制更新,還會阻礙事件處理(比如影響觸摸事件)稚疹。只要代碼運行在主線程程居灯,應(yīng)用對于用戶來說就是“功能掛起來的”祭务。如果主線程歷程返回足夠快,這些變化根本就察覺不到怪嫌。

可能會想:“那就在后臺線程運行我的繪圖指令”义锥。但是通常是無法做到這一點的,因為對于當(dāng)前的UIKit上下文來說繪圖不是線程安全的岩灭。任何在后臺線程修改視圖的嘗試都會導(dǎo)致未定義的行為拌倍,包括繪制出錯或崩潰。

這個行為并不需要克服噪径。繪圖事件實際是iOS在有限的硬件上渲染復(fù)雜繪圖的功能柱恤。如你會在本章看到的,UIKit中很多東西都要避免不必要的繪制找爱,這是最開始步驟中的一個環(huán)節(jié)梗顺。那么,要如何開始和停止一個長時間運行操作的活動的指示器呢车摄?可以采用調(diào)度(dispatch)或執(zhí)行隊列事將耗時的任務(wù)放入后臺寺谤,同時創(chuàng)建如下在主線程中進行UIKit調(diào)用的代碼。

ViewController.m(TimeConsuming)###

- (IBAction)doSomething: (id)sender {
  [sender setEnabled:NO];
  [self.activity startAnimatiing];

  dispatch_queue_t bgQueue =     dispatch_get_global_queue{DISPATCH_QUEUE_PRIORITY_DEFAULT,0};

  dispatch_async(bgQueue,^{
    [self somethingTimeConsuming];
    dispatch_async(dispatch_get_main_queue(),^{
        [self.activity stopAnimating];
        [sender setEnabled:YES];
    }) ;
});
}

調(diào)用IBAction后吮播,便可以創(chuàng)建活動指示器的動畫效果变屁。然后,將對somethingTimeConsuming的調(diào)用放入默認的后臺調(diào)度隊列意狠。完成后粟关,可以把stopAnimating的調(diào)度放入主調(diào)度隊列。調(diào)度和執(zhí)行隊列會在之后介紹摄职。

簡而言之誊役,我們得出了一下結(jié)論
1.iOS 在運行循環(huán)(run loop)中整合所有的繪制請求,并以此將它繪制出來谷市。
2.不能在主線程中進行復(fù)雜的處理蛔垢。
3.不能在主線程之外的主視圖上線文中繪制。你需要檢查每個UIKit方法以確保它沒有主線程需求迫悠。只要不是在主視圖上下文中繪制鹏漆,一些UIKit方法是可以在后臺線程中使用的。

視圖繪制與視圖布局###

UIView將子視圖的布局(“重新排列”)從繪圖(“顯示”)中獨立出來创泄。這對于最大程度低優(yōu)化性能很重要艺玲,因為布局的成本要比繪制低。布局之所以成本低鞠抑,是因為UIView的緩存銅鼓GPU優(yōu)化的位圖進行繪圖操作饭聚。使用GPU,可以使這些位圖的移動搁拙,顯示秒梳,隱藏法绵,旋轉(zhuǎn),甚至變形和合并的成本都非常低酪碘。

當(dāng)你對一個視圖調(diào)用了setNeedsDisplay方法朋譬,它就被標記為“需要刷新的”,并且會在下一次繪圖周期中重新繪制兴垦。除非視圖的內(nèi)容真的會發(fā)生變化徙赢,否則請不要調(diào)用它。大部分UIKit視圖會在其數(shù)據(jù)發(fā)生變化時自動管理重繪操作探越,因此除了自定義的視圖狡赐,一般并不需要調(diào)用setNeedsDisplay方法。

當(dāng)旋轉(zhuǎn)設(shè)備或者滾動視圖時扶关,子視圖就需要重新排列了阴汇,UIKit會調(diào)用setNeedsLayout方法;也就是對發(fā)生變化的視圖逐次調(diào)用layoutSubviews方法节槐。重寫layoutSubviews方法,就會讓應(yīng)用在設(shè)備旋轉(zhuǎn)或者視圖滾動的時候更加流暢拐纱。你可以不必重繪他們铜异,就可以重新排列子視圖的位置,而且還可以根據(jù)設(shè)備方向隱藏或者顯示視圖秸架。如果數(shù)據(jù)改變后揍庄,只需要進行視圖更新(而不是繪制),只需要調(diào)用setNeedsLayout方法东抹。

自定義視圖的繪制###

視圖可以通過子視圖蚂子,圖層,drawRect:方法來表現(xiàn)內(nèi)容缭黔。通常來說食茎,如果實現(xiàn)了drawRect:方法,最好就不要在混用圖層和子視圖了馏谨,即使這樣做合法而且有很大幫助别渔。大部分自定義繪圖都是用UIKit或者Core Graphics實現(xiàn)的,雖然OpenGL ES 更易于集成惧互。

2D繪圖一般可以拆分成以下幾個步驟操作:
線條:
路徑(填充或輪廓圖形)
文本
圖標
漸變
2D繪圖不能夠操作單獨的像素哎媚,因為像素是依賴于目標的。你可以從位圖上下文中讀取它喊儡,但無法使用UIKit或者Core Graphics函數(shù)來直接作用于它拨与。

UIKit和Core Graphics都是用"pointer"繪圖模型。這就意味著每個命令都是依次繪制并且在事件循環(huán)中在上一次繪圖上疊加內(nèi)容艾猜。在這個模型中順序是非常重要的买喧,必須從底層開始向上繪制攀甚。每次調(diào)用drawRect方法,都要對所需要的區(qū)域進行繪制岗喉。在調(diào)用drawRect方法時秋度,繪圖畫布并不受到保護。

通過UIKit繪圖###

在iPad出來之前钱床,大部分自定義繪圖都只能使用Core Graphics荚斯,因為使用UIKit并不能繪制任意形狀。在iPhone OS 3.2系統(tǒng)中查牌,蘋果添加了UIBezierPath并使其更易于通過Objective-C來繪制事期。UIKit依然缺乏對漸變,線條纸颜,陰影以及一些高級特性(比如控制反鋸齒和精確顏色管理)的支持兽泣。即便如此,UIKit如今仍然是一個非常方便實現(xiàn)大部分自定義繪圖需要的方式胁孙。

繪制矩形的最簡單辦法是使用UIRectFrame或UIRectFill唠倦,如以下代碼所示:

- (void)drawrRect:(CGRect)rect{
   [[UIColor redColor] setFill];
   UIRectFill(CGRectMake(10,10,100,10));
}

需要注意,你首先是如何通過[UIColor setFill]設(shè)置畫筆顏色的涮较。繪圖在調(diào)用drawRect方法前稠鼻,會在系統(tǒng)提供的上下文中完成。這個上下文中含有大量的信息狂票,包括畫筆顏色候齿,填充顏色,文本顏色闺属,字體慌盯,形狀等。同一時間內(nèi)掂器,只有一支畫筆和一支填充筆亚皂。他們的顏色可以繪制任何東西。

繪圖是依賴順序的唉匾。其中更包括畫筆的命令孕讳。

體重drawRect方法的圖形上下文,是特殊的視圖圖形上下文巍膘。還有其他類型的圖形上下文厂财,包括PDF以及位圖上下文。所有這些都是使用同樣的繪制技術(shù)峡懈,不過視圖上下文是針對屏幕的繪制進行優(yōu)化的璃饱。

路徑###

UIKit包含了很多要比他的矩形繪制函數(shù)更加強大的繪圖命令。它可以通過UIBezizerPath繪制任意曲線和線條肪康。貝塞爾曲線是使用了一些觸點的線條和曲線的數(shù)學(xué)表達方式荚恶。一般情況下撩穿,你不需要擔(dān)心自己的數(shù)學(xué)水平,以為UIBezizerPath擁有處理大部分常見路徑(線條谒撼,弧線食寡,矩形或圓角矩形,橢圓)的簡單方法廓潜。通過這些路徑抵皱,你可以快速繪制大部分UI元素形狀。以下代碼是一個簡單相撞縮放填充圖形的實例辩蛋。

FlowerView.m(Paths)######
- (void)drawRect:(CGRect)rect {
   CGSize size = self.bounds.size;
   CGFloat margin = 10;
   CGFloat radius = rint(MIN(size.height - margin, size.width - margin)/4);
   CGFloat xOffset,yOffset;
   CGFloat offset = rint((size.height - size.height)/2);
   if (offset > 0) {
          xOffset = rint(margin / 2);
          yOffset = offset;
   }
  else {
        xoffset = -offset;
        yoffset = rint(margin / 2);
 }
     [[UIColor redColor] setFill];  
     UIBezierPath * path = [UIBezier bezierPath];
     [path addArchWithCenter:CGPointMake(radius * 2 + xOffset,adius + yOffset) radius startAngle:-M_PI endAngle:0 colckwise:YES];
     [path addArcWithCenter:CGPointMake(radius * 3 + xoffset, radius * 2 + yOffset) radius:radius startAngle:-M_PI_2 endAngle:-M_PI_2 clockwise:YES];
     [path addArcWithCenter:CGPointMake(radius * 2 + xOffset, adius * 3 + yOffset) radius:radius startAngle:0 endAngle:M_PI colokwise:YES];
    [paht addArcWithcenter:CGPointMake(radius + xOffset,adius * 2 + yOffset) radius:radius startAngle:M_PI_2 endAngle:-M_PI_2 clockwise:YES];
     [path closePath];
     [path fill];
}

FlowerView創(chuàng)建了由一系列弧線組成的路徑呻畸,并用紅色進行填充。創(chuàng)建路徑并不會導(dǎo)致繪制任何內(nèi)容悼院。UIBezierPath只是一系列弧線伤为,就好像NSString是一系列的字符。只有調(diào)用fill据途,弧線才會被繪制在當(dāng)前的上下文中绞愚。

請注意M_PI(π)以及M_PI/2(π/2)常量的使用∽蚍玻弧線是由弧度表示的爽醋。因此π以及它的分數(shù)很重要。math.h定義了很多這樣的常量便脊,你可以直接使用它們而不必要在計算出來」飧辏弧線使用順時針角度哪痰,認為0弧度指向右邊,π/2弧度指向下方久妆,π(或者-π)指向左邊晌杰,而-π/2弧度指向上方。如果你愿意的話筷弦,也可以使用3π/2來表示向上肋演,不過我認為-M_PI_2比3*M_PI_2更易于理解。如果弧度讓你頭疼的話烂琴,可以創(chuàng)建這個函數(shù):

CGFloat RadiansFromDegrees(CGFloat d)
{
    return d * M_PI / 180;
}

我覺得習(xí)慣使用弧度要比做數(shù)學(xué)計算好多了爹殊,不過你如果需要特殊的角度,那么使用角度要更簡單一些奸绷。

在計算radius 和 offset時梗夸,可以使用rint(四舍五入)來確保對齊(這樣就會像素對齊)。這可以幫你改善性能号醉,并且可以避免模糊的邊緣反症。所以大部分情況下都如你所愿辛块,不過萬一某一條弧線碰到了線條,它會導(dǎo)致差一錯誤铅碍。通常最好的辦法是移動線條以便所有的值都是整數(shù)润绵。

理解坐標系###

坐標,點和像素之間的微妙轉(zhuǎn)化胞谈,也可能降低繪制的性能尘盼。導(dǎo)致線條和文字模糊。觀察一下代碼呜魄。

CGContextSetLineWidth(context,3.);
//繪制從坐標{10悔叽,100}到{200,100}的3像素水平線條
CGContextMoveToPoint(context,10.,100.);
CGContextAddLineToPoint(context,200.,100.);
CGContextStrokePath(context);
//繪制從坐標{10,105.5} 到 {200爵嗅,105.5}的3像素寬水平線條
CGContextMoveToPoint(context,10.,105.5);
CGContextAddLineTopoint(context,200.,105.);
CGContextStokePath(context);

比較從{10娇澎,100} 和{10,105.5}出發(fā)的兩條線。從 {10睹晒,100}到{200趟庄,100}的線條要比{10,100.5} 到{200伪很,100}的線條要模糊的多戚啥,原因就是iOS對坐標系的解讀方式。

構(gòu)造一個CGPath時锉试,便是使用了所謂的幾何坐標系猫十。這與數(shù)學(xué)中使用的坐標系是一樣的。以兩條網(wǎng)格線的交點來表示零坐標點呆盖。你無法繪制出真正的幾何點或者幾何線條拖云。因為他們都是無限小和無限細的。iOS繪制中应又,必須將這些幾何對象轉(zhuǎn)換成像素坐標宙项。這是一個可以指定顏色的2D網(wǎng)絡(luò)。像素是設(shè)備能控制的最小的顯示區(qū)域單位株扛。

當(dāng)調(diào)用了CGContextStokePath尤筐,iOS會讓線條路徑居中。理想情況下洞就,線條有3像素寬盆繁,從y = 98.5到y(tǒng)=101.5。danshi ,但是這個線條仍然能不繪制奖磁。每個像素必須是唯一的顏色改基。一半是畫筆顏色,一半是背景顏色。iOS通過兩個顏色的平均值解決了這個問題秕狰。同樣的技術(shù)也用在了反鋸齒上稠腊。

在屏幕上,線條看起來會有些模糊鸣哀。解決這個問題的方法是將水平或者垂直的線條移動到半個點的位置架忌。這樣當(dāng)iOS 線條居中的時候。邊緣剛好就是像素的邊界我衬√痉牛或者可以讓線條更粗一些。

使用非整形寬度的線條挠羔,或者坐標系不是整形或者半整形時候井仰,也可能遇到這樣的問題。讓iOS繪制小數(shù)像素時破加,都可能導(dǎo)致模糊俱恶。

填充工具與畫筆不一樣。畫筆的線條是中心對齊路徑的范舀。而填充是基于路徑的合是。如果填充從{10,100}到{200锭环,103}的矩形聪全,每個像素都會被正確填充。

目前討論的視點與像素相同辅辩。而在Retain屏幕上难礼,他們就不一樣了。iPhone 4的每個點有4個像素玫锋,縮放比例為2鹤竭。這樣的事情就有了一些微妙的變化。而且通常是情況更好了景醇。因為Core Graphics 和 UIKit的坐標都是用點來表示的。所有整數(shù)寬度的線條都以偶數(shù)個像素的個數(shù)來表示了吝岭。比如說三痰,如果需要一個點寬的畫筆,實際上就是一個兩像素的畫筆窜管。繪制這條線散劫,iOS需要填充路徑兩邊的像素。這樣就是整數(shù)的像素了幕帆。因此不需要反鋸齒處理获搏。當(dāng)然,如果使用的坐標系不是整數(shù)或半整數(shù)失乾。依然有可能遇到模糊的情況常熙。

在Retina屏幕上纬乍,并不需要位移半個點的位置。不過這不會有影響裸卫。若是要支持iPhone 3GS或iPad2仿贬。便需要對水平或垂直線條使用半個點的位移。

只能對水平或者垂直線條使用這些方法墓贿。斜線和曲線應(yīng)該進行反鋸齒處理以便不會出現(xiàn)缺口茧泪。沒有必要為他們進行偏移操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末聋袋,一起剝皮案震驚了整個濱河市队伟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幽勒,老刑警劉巖嗜侮,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異代嗤,居然都是意外死亡棘钞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進店門干毅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宜猜,“玉大人,你說我怎么就攤上這事硝逢∫逃担” “怎么了?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵渠鸽,是天一觀的道長叫乌。 經(jīng)常有香客問我,道長徽缚,這世上最難降的妖魔是什么憨奸? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮凿试,結(jié)果婚禮上排宰,老公的妹妹穿的比我還像新娘。我一直安慰自己那婉,他們只是感情好板甘,可當(dāng)我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著详炬,像睡著了一般盐类。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天在跳,我揣著相機與錄音枪萄,去河邊找鬼。 笑死硬毕,一個胖子當(dāng)著我的面吹牛呻引,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吐咳,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼逻悠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了韭脊?” 一聲冷哼從身側(cè)響起童谒,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沪羔,沒想到半個月后饥伊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡蔫饰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年琅豆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篓吁。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡茫因,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杖剪,到底是詐尸還是另有隱情冻押,我是刑警寧澤,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布盛嘿,位于F島的核電站洛巢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏次兆。R本人自食惡果不足惜稿茉,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芥炭。 院中可真熱鬧狈邑,春花似錦、人聲如沸蚤认。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砰琢。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陪汽,已是汗流浹背训唱。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挚冤,地道東北人况增。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像训挡,于是被迫代替她去往敵國和親澳骤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,930評論 2 361

推薦閱讀更多精彩內(nèi)容

  • Core Graphics Framework是一套基于C的API框架澜薄,使用了Quartz作為繪圖引擎为肮。它提供了低...
    ShanJiJi閱讀 1,542評論 0 20
  • 原文地址:http://www.cocoachina.com/industry/20140115/7703.htm...
    默默_David閱讀 6,111評論 0 1
  • (1)Time Profiler:用來測量被方法/函數(shù)打斷的CPU使用情況。 (2)Core Animation:...
    錢噓噓閱讀 1,478評論 2 6
  • 昨晚做了一個繁亂的夢肤京。早晨醒來幾乎都忘了颊艳,后來不知道怎么想起來,越想越覺得奇怪忘分,又覺得和自己某種思緒暗合棋枕。 夢里,...
    真冉閱讀 201評論 0 1
  • 這幾天天氣實在是太熱了,大概半個月的時間沒有在家里面睡覺了舟山,媳婦去了大姐家蹭空調(diào)绸狐,我呢,跑到他們辦的輔導(dǎo)班里面去打...
    清風(fēng)未央閱讀 224評論 0 0