- (void) drawRect: (CGRect) rect {  UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];  [[UIColor blueColor] setFill];  [p fill];  }  第二種繪圖形式:使用Core Graphics實(shí)現(xiàn)繪制藍(lán)色圓。 - (void) drawRect: (CGRect) rect {  CGContextRef con = UIGraphicsGetCurrentContext();  CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));  CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);  CGContextFillPath(con);  }  第三種繪圖形式:我將在UIView子類的drawLayer:inContext:方法中實(shí)現(xiàn)繪圖任務(wù)。drawLayer:inContext:方法是一個(gè)繪制圖層內(nèi)容的代理方法。為了能夠調(diào)用drawLayer:inContext:方法,我們需要設(shè)定圖層的代理對(duì)象。但要注意,不應(yīng)該將UIView對(duì)象設(shè)置為顯示層的委托對(duì)象,這是因?yàn)閁IView對(duì)象已經(jīng)是隱式層的代理對(duì)象,再將它設(shè)置為另一個(gè)層的委托對(duì)象就會(huì)出問(wèn)題。輕量級(jí)的做法是:編寫(xiě)負(fù)責(zé)繪圖形的代理類。在MyView.h文件中聲明如下代碼: @interface MyLayerDelegate : NSObject  @end  然后MyView.m文件中實(shí)現(xiàn)接口代碼: @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  直接將代理類的實(shí)現(xiàn)代碼放在MyView.m文件的#import代碼的下面,這樣感覺(jué)好像在使用私有類完成繪圖任務(wù)(雖然這不是私有類)。需要注意的是,我們所引用的上下文并不是當(dāng)前上下文,所以為了能夠使用UIKit,我們需要將引用的上下文轉(zhuǎn)變成當(dāng)前上下文。 因?yàn)閳D層的代理是assign內(nèi)存管理策略,那么這里就不能以局部變量的形式創(chuàng)建MyLayerDelegate實(shí)例對(duì)象賦值給圖層代理。這里選擇在MyView.m中增加一個(gè)實(shí)例變量,因?yàn)閷?shí)例變量默認(rèn)是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]; // 調(diào)用此方法,drawLayer: inContext:方法才會(huì)被調(diào)用。  第四種繪圖形式: 使用Core Graphics在drawLayer:inContext:方法中實(shí)現(xiàn)同樣操作,代碼如下: - (void)drawLayer:(CALayer*)lay inContext:(CGContextRef)con {  CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));  CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);  CGContextFillPath(con);  }  最后,演示UIGraphicsBeginImageContextWithOptions的用法,并從上下文中生成一個(gè)UIImage對(duì)象。生成UIImage對(duì)象的代碼并不需要等待某些方法被調(diào)用后或在UIView的子類中才能去做。 第五種繪圖形式: 使用UIKit實(shí)現(xiàn): 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函數(shù)參數(shù)的含義:第一個(gè)參數(shù)表示所要?jiǎng)?chuàng)建的圖片的尺寸;第二個(gè)參數(shù)用來(lái)指定所生成圖片的背景是否為不透明,如上我們使用YES而不是NO,則我們得到的圖片背景將會(huì)是黑色,顯然這不是我想要的;第三個(gè)參數(shù)指定生成圖片的縮放因子,這個(gè)縮放因子與UIImage的scale屬性所指的含義是一致的。傳入0則表示讓圖片的縮放因子根據(jù)屏幕的分辨率而變化,所以我們得到的圖片不管是在單分辨率還是視網(wǎng)膜屏上看起來(lái)都會(huì)很好。 第六種繪圖形式: 使用Core Graphics實(shí)現(xiàn): 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被認(rèn)為是線程不安全的。而在iOS4.0以后蘋(píng)果讓繪圖操作在第二個(gè)線程中執(zhí)行解決了此問(wèn)題。 UIImage常用的繪圖操作 一個(gè)UIImage對(duì)象提供了向當(dāng)前上下文繪制自身的方法。我們現(xiàn)在已經(jīng)知道如何獲取一個(gè)圖片類型的上下文并將它轉(zhuǎn)變成當(dāng)前上下文。 平移操作:下面的代碼展示了如何將UIImage繪制在當(dāng)前的上下文中。 UIImage* mars = [UIImage imageNamed:@"Mars.png"];  CGSize sz = [mars size];  UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*2, sz.height), NO, 0);  [mars drawAtPoint:CGPointMake(0,0)];  [mars drawAtPoint:CGPointMake(sz.width,0)];  UIImage* im = UIGraphicsGetImageFromCurrentImageContext();  UIGraphicsEndImageContext();  UIImageView* iv = [[UIImageView alloc] initWithImage:im];  [self.window.rootViewController.view addSubview: iv];      iv.center = self.window.center;  圖1 UIImage平移處理 縮放操作:下面代碼展示了如何對(duì)UIImage進(jìn)行縮放操作:
圖1 UIImage平移處理 縮放操作:下面代碼展示了如何對(duì)UIImage進(jìn)行縮放操作:UIImage* mars = [UIImage imageNamed:@"Mars.png"];  CGSize sz = [mars size];  UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*2, sz.height*2), NO, 0);  [mars drawInRect:CGRectMake(0,0,sz.width*2,sz.height*2)];  [mars drawInRect:CGRectMake(sz.width/2.0, sz.height/2.0, sz.width, sz.height) blendMode:kCGBlendModeMultiply alpha:1.0];  UIImage* im = UIGraphicsGetImageFromCurrentImageContext();  UIGraphicsEndImageContext();    圖2 UIImage縮放處理 UIImage沒(méi)有提供截取圖片指定區(qū)域的功能。但通過(guò)創(chuàng)建一個(gè)較小的圖形上下文并移動(dòng)圖片到一個(gè)適當(dāng)?shù)膱D形上下文坐標(biāo)系內(nèi),指定區(qū)域內(nèi)的圖片就會(huì)被獲取。 裁剪操作:下面代碼展示了如何獲取圖片的右半邊:
圖2 UIImage縮放處理 UIImage沒(méi)有提供截取圖片指定區(qū)域的功能。但通過(guò)創(chuàng)建一個(gè)較小的圖形上下文并移動(dòng)圖片到一個(gè)適當(dāng)?shù)膱D形上下文坐標(biāo)系內(nèi),指定區(qū)域內(nèi)的圖片就會(huì)被獲取。 裁剪操作:下面代碼展示了如何獲取圖片的右半邊:UIImage* mars = [UIImage imageNamed:@"Mars.png"];  CGSize sz = [mars size];  UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width/2.0, sz.height), NO, 0);  [mars drawAtPoint:CGPointMake(-sz.width/2.0, 0)];  UIImage* im = UIGraphicsGetImageFromCurrentImageContext();  UIGraphicsEndImageContext();  以上的代碼首先創(chuàng)建一個(gè)一半圖片寬度的圖形上下文,然后將圖片左上角原點(diǎn)移動(dòng)到與圖形上下文負(fù)X坐標(biāo)對(duì)齊,從而讓圖片只有右半部分與圖形上下文相交。 圖3 UIImage裁剪原理 CGImage常用的繪圖操作UIImage的Core Graphics版本是CGImage(具體類型是CGImageRef)。兩者可以直接相互轉(zhuǎn)化: 使用UIImage的CGImage屬性可以訪問(wèn)Quartz圖片數(shù)據(jù);將CGImage作為UIImage方法imageWithCGImage:或initWithCGImage:的參數(shù)創(chuàng)建UIImage對(duì)象。 一個(gè)CGImage對(duì)象可以讓你獲取原始圖片中指定區(qū)域的圖片(也可以獲取指定區(qū)域外的圖片,UIImage卻辦不到)。 下面的代碼展示了將圖片拆分成兩半,并分別繪制在上下文的左右兩邊:
 圖3 UIImage裁剪原理 CGImage常用的繪圖操作UIImage的Core Graphics版本是CGImage(具體類型是CGImageRef)。兩者可以直接相互轉(zhuǎn)化: 使用UIImage的CGImage屬性可以訪問(wèn)Quartz圖片數(shù)據(jù);將CGImage作為UIImage方法imageWithCGImage:或initWithCGImage:的參數(shù)創(chuàng)建UIImage對(duì)象。 一個(gè)CGImage對(duì)象可以讓你獲取原始圖片中指定區(qū)域的圖片(也可以獲取指定區(qū)域外的圖片,UIImage卻辦不到)。 下面的代碼展示了將圖片拆分成兩半,并分別繪制在上下文的左右兩邊:UIImage* mars = [UIImage imageNamed:@"Mars.png"];  // 抽取圖片的左右半邊  CGSize sz = [mars size];  CGImageRef marsLeft = CGImageCreateWithImageInRect([mars CGImage],CGRectMake(0,0,sz.width/2.0,sz.height));  CGImageRef marsRight = CGImageCreateWithImageInRect([mars CGImage],CGRectMake(sz.width/2.0,0,sz.width/2.0,sz.height));  // 將每一個(gè)CGImage繪制到圖形上下文中  UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0);  CGContextRef con = UIGraphicsGetCurrentContext();  CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height), marsLeft);  CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height), marsRight);  UIImage* im = UIGraphicsGetImageFromCurrentImageContext();  UIGraphicsEndImageContext();  // 記得釋放內(nèi)存,ARC在這里無(wú)效  CGImageRelease(marsLeft);  CGImageRelease(marsRight);  你也許發(fā)現(xiàn)繪出的圖是上下顛倒的!圖片的顛倒并不是因?yàn)楸恍D(zhuǎn)了。當(dāng)你創(chuàng)建了一個(gè)CGImage并使用CGContextDrawImage方法繪圖就會(huì)引起這種問(wèn)題。這主要是因?yàn)樵嫉谋镜刈鴺?biāo)系統(tǒng)(坐標(biāo)原點(diǎn)在左上角)與目標(biāo)上下文(坐標(biāo)原點(diǎn)在左下角)不匹配。有很多方法可以修復(fù)這個(gè)問(wèn)題,其中一種方法就是使用CGContextDrawImage方法先將CGImage繪制到UIImage上,然后獲取UIImage對(duì)應(yīng)的CGImage,此時(shí)就得到了一個(gè)倒轉(zhuǎn)的CGImage。當(dāng)再調(diào)用CGContextDrawImage方法,我們就將倒轉(zhuǎn)的圖片還原回來(lái)了。實(shí)現(xiàn)代碼如下: CGImageRef flip (CGImageRef im) {  CGSize sz = CGSizeMake(CGImageGetWidth(im), CGImageGetHeight(im));  UIGraphicsBeginImageContextWithOptions(sz, NO, 0);  CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, sz.width, sz.height), im);  CGImageRef result = [UIGraphicsGetImageFromCurrentImageContext() CGImage];  UIGraphicsEndImageContext();  return result;  }  現(xiàn)在將之前的代碼修改如下: CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height), flip(marsLeft));  CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height), flip(marsRight));  然而,這里又出現(xiàn)了另外一個(gè)問(wèn)題:在雙分辨率的設(shè)備上,如果我們的圖片文件是高分辨率(@2x)版本,上面的繪圖就是錯(cuò)誤的。原因在于對(duì)于UIImage來(lái)說(shuō),在加載原始圖片時(shí)使用imageNamed:方法,它會(huì)自動(dòng)根據(jù)所在設(shè)備的分辨率類型選擇圖片,并且UIImage通過(guò)設(shè)置用來(lái)適配的scale屬性補(bǔ)償圖片的兩倍尺寸。但是一個(gè)CGImage對(duì)象并沒(méi)有scale屬性,它不知道圖片文件的尺寸是否為兩倍!所以當(dāng)調(diào)用UIImage的CGImage方法,你不能假定所獲得的CGImage尺寸與原始UIImage是一樣的。在單分辨率和雙分辨率下,一個(gè)UIImage對(duì)象的size屬性值都是一樣的,但是雙分辨率UIImage對(duì)應(yīng)的CGImage是單分辨率UIImage對(duì)應(yīng)的CGImage的兩倍大。所以我們需要修改上面的代碼,讓其在單雙分辨率下都可以工作。代碼如下: UIImage* mars = [UIImage imageNamed:@"Mars.png"];  CGSize sz = [mars size];  // 轉(zhuǎn)換CGImage并使用對(duì)應(yīng)的CGImage尺寸截取圖片的左右部分  CGImageRef marsCG = [mars CGImage];  CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG), CGImageGetHeight(marsCG));  CGImageRef marsLeft = CGImageCreateWithImageInRect(marsCG,CGRectMake(0,0,szCG.width/2.0,szCG.height));  CGImageRef marsRight = CGImageCreateWithImageInRect(marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height));  UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0);  //剩下的和之前的代碼一樣,修復(fù)倒置問(wèn)題  CGContextRef con = UIGraphicsGetCurrentContext();  CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height),flip(marsLeft));  CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height),flip(marsRight));  UIImage* im = UIGraphicsGetImageFromCurrentImageContext();  UIGraphicsEndImageContext();  CGImageRelease(marsLeft);  CGImageRelease(marsRight);  上面的代碼初看上去很繁雜,不過(guò)不用擔(dān)心,這里還有另一種修復(fù)倒置問(wèn)題的方案。相對(duì)于使用flip函數(shù),你可以在繪圖之前將CGImage包裝進(jìn)UIImage中,這樣做有兩大優(yōu)點(diǎn):1.當(dāng)UIImage繪圖時(shí)它會(huì)自動(dòng)修復(fù)倒置問(wèn)題2.當(dāng)你從CGImage轉(zhuǎn)化為Uimage時(shí),可調(diào)用imageWithCGImage:scale:orientation:方法生成CGImage作為對(duì)縮放性的補(bǔ)償。 所以這是一個(gè)解決倒置和縮放問(wèn)題的自包含方法。 代碼如下:UIImage* mars = [UIImage imageNamed:@"Mars.png"];  CGSize sz = [mars size];  CGImageRef marsCG = [mars CGImage];  CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG), CGImageGetHeight(marsCG));  CGImageRef marsLeft = CGImageCreateWithImageInRect(marsCG, CGRectMake(0,0,szCG.width/2.0,szCG.height));  CGImageRef marsRight = CGImageCreateWithImageInRect(marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height));  UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0);  [[UIImage imageWithCGImage:marsLeft scale:[mars scale] orientation:UIImageOrientationUp] drawAtPoint:CGPointMake(0,0)];  [[UIImage imageWithCGImage:marsRight scale:[mars scale] orientation:UIImageOrientationUp] drawAtPoint:CGPointMake(sz.width,0)];  UIImage* im = UIGraphicsGetImageFromCurrentImageContext();  UIGraphicsEndImageContext();  CGImageRelease(marsLeft); CGImageRelease(marsRight);   還有另一種解決倒置問(wèn)題的方案是在繪制CGImage之前,對(duì)上下文應(yīng)用變換操作,有效地倒置上下文的內(nèi)部坐標(biāo)系統(tǒng)。這里先不做討論。 為什么會(huì)發(fā)生倒置問(wèn)題究其原因是因?yàn)镃ore Graphics源于Mac OS X系統(tǒng),在Mac OS X中,坐標(biāo)原點(diǎn)在左下方并且正y坐標(biāo)是朝上的,而在iOS中,原點(diǎn)坐標(biāo)是在左上方并且正y坐標(biāo)是朝下的。在大多數(shù)情況下,這不會(huì)出現(xiàn)任何問(wèn)題,因?yàn)閳D形上下文的坐標(biāo)系統(tǒng)是會(huì)自動(dòng)調(diào)節(jié)補(bǔ)償?shù)摹5莿?chuàng)建和繪制一個(gè)CGImage對(duì)象時(shí)就會(huì)暴露出倒置問(wèn)題。 CIFilter與CIImageCIFilter與CIImage是iOS 5新引入的,雖然它們已在MAX OS X系統(tǒng)中存在多年。前綴“CI”表示Core Image,這是一種使用數(shù)學(xué)濾鏡變換圖片的技術(shù)。但是你不要去幻想iOS提供了像photoshop軟件那樣強(qiáng)大的濾鏡功能。使用Core Image之前你需要將CoreImage.framework框架導(dǎo)入到你的target之中。 所謂濾鏡指的是CIFilter類,濾鏡可被分為以下幾類: 模板與漸變類這兩類濾鏡創(chuàng)建的CIImage可以和其他的CIImage進(jìn)行合并,比如一種單色,一個(gè)棋盤(pán),條紋,亦或是漸變。 合成類此類濾鏡可以將一張圖片與另外的圖片合并,合成濾鏡模式常見(jiàn)于圖形處理軟件Photoshop中。 色彩類此濾鏡調(diào)整、修改圖片的色彩。因此你可以改變一張圖片的飽和度、色度、亮度、對(duì)比度、伽馬、白點(diǎn)、曝光度、陰影、高亮等屬性。 幾何變換類此類濾鏡可對(duì)圖片執(zhí)行基本的幾何變換,比如縮放、旋轉(zhuǎn)、裁剪。 CIFilter使用起來(lái)非常的簡(jiǎn)單。CIFilter看上去就像一個(gè)由鍵值組成的字典。它生成一個(gè)CIImage對(duì)象作為其輸出。一般地,一個(gè)濾鏡有一個(gè)或多個(gè)輸入,而對(duì)于部分濾鏡,生成的圖片是基于其他類型的參數(shù)值。CIFilter對(duì)象是一個(gè)集合,可使用鍵值對(duì)進(jìn)行檢索。通過(guò)提供濾鏡的字符串名稱創(chuàng)建一個(gè)濾鏡,如果想知道有哪些濾鏡,可以查詢蘋(píng)果的Core Image Filter Reference文檔,或是調(diào)用CIFilter的類方法filterNamesInCategories:,參數(shù)值為nil。每一個(gè)濾鏡擁有一小部分用來(lái)確定其行為的鍵值。如果你想修改某一個(gè)鍵(比如亮度鍵)對(duì)應(yīng)的值,你可以調(diào)用setValue:forKey:方法或當(dāng)你指定一個(gè)濾鏡名時(shí)提供所有鍵值對(duì)。 需要處理的圖片必須是CIImage類型,調(diào)用initWithCGImage:方法可獲得CIImage。因?yàn)镃GImage又是作為濾鏡的輸出,因此濾鏡之間可被連接在一起(將濾鏡的輸出作為initWithCGImage:方法的輸入?yún)?shù)) 當(dāng)你構(gòu)建一個(gè)濾鏡鏈時(shí),并沒(méi)有做復(fù)雜的運(yùn)算。只有當(dāng)整個(gè)濾鏡鏈需要輸出一個(gè)CGImage時(shí),密集型計(jì)算才會(huì)發(fā)生。調(diào)用contextWithOptions:和createCGImage: fromRect:方法創(chuàng)建CIContext。與以往不同的地方是CIImage沒(méi)有frame與bounds屬性;只有extent屬性。你將非常頻繁的使用這個(gè)屬性作為createCGImage: fromRect:方法的第二個(gè)參數(shù)。 接下來(lái)我將演示Core Image的使用。首先創(chuàng)建一個(gè)徑向漸變的濾鏡,該濾鏡是從白到黑的漸變方式,白色區(qū)域的半徑默認(rèn)是100。接著將其與一張使用CIDarkenBlendMode濾鏡的圖片合成。CIDarkenBlendMode的作用是背景圖片樣本將被源圖片的黑色部分替換掉。 代碼如下: UIImage* moi = [UIImage imageNamed:@"Mars.jpeg"];  CIImage* moi2 = [[CIImage alloc] initWithCGImage:moi.CGImage];  CIFilter* grad = [CIFilter filterWithName:@"CIRadialGradient"];  CIVector* center = [CIVector vectorWithX:moi.size.width / 2.0 Y:moi.size.height / 2.0];  // 使用setValue:forKey:方法設(shè)置濾鏡屬性  [grad setValue:center forKey:@"inputCenter"];  // 在指定濾鏡名時(shí)提供所有濾鏡鍵值對(duì)  CIFilter* dark = [CIFilter filterWithName:@"CIDarkenBlendMode" keysAndValues:@"inputImage", grad.outputImage, @"inputBackgroundImage", moi2, nil];  CIContext* c = [CIContext contextWithOptions:nil];  CGImageRef moi3 = [c createCGImage:dark.outputImage fromRect:moi2.extent];  UIImage* moi4 = [UIImage imageWithCGImage:moi3 scale:moi.scale orientation:moi.imageOrientation];  CGImageRelease(moi3);    圖4 圖片合成快照 這個(gè)例子可能沒(méi)有什么吸引人的地方,因?yàn)樗幸磺卸伎梢允褂肅ore Graphics完成。除了Core Image是使用GPU處理,可能有點(diǎn)吸引人。Core Graphics也可以做到徑向漸變并使用混合模式合成圖片。但Core Image要簡(jiǎn)單得多,特別是當(dāng)你有多個(gè)圖片輸入想重用一個(gè)濾鏡鏈時(shí)。并且Core Image的顏色調(diào)整功能比Core Graphics更加強(qiáng)大。對(duì)了,Core Image還能實(shí)現(xiàn)自動(dòng)人臉識(shí)別哦! 繪制一個(gè)UIView繪制一個(gè)UIVIew最靈活的方式就是由它自己完成繪制。實(shí)際上你不是繪制一個(gè)UIView,你只是子類化了UIView并賦予子類繪制自己的能力。當(dāng)一個(gè)UIVIew需要執(zhí)行繪圖操作的時(shí), drawRect:方法就會(huì)被調(diào)用。覆蓋此方法讓你獲得繪圖操作的機(jī)會(huì)。當(dāng)drawRect:方法被調(diào)用,當(dāng)前圖形上下文也被設(shè)置為屬于視圖的圖形上下文。你可以使用Core Graphics或UIKit提供的方法將圖形畫(huà)到該上下文中。 你不應(yīng)該手動(dòng)調(diào)用drawRect:方法!如果你想調(diào)用drawRect:方法更新視圖,只需發(fā)送setNeedsDisplay方法。這將使得drawRect:方法會(huì)在下一個(gè)適當(dāng)?shù)臅r(shí)間調(diào)用。當(dāng)然,不要覆蓋drawRect:方法除非你知道這樣做絕對(duì)合法。比方說(shuō),在UIImageView子類中覆蓋drawRect:方法是不合法的,你將得不到你繪制的圖形。 在UIView子類的drawRect:方法中無(wú)需調(diào)用super,因?yàn)楸旧鞺IView的drawRect:方法是空的。為了提高一些繪圖性能,你可以調(diào)用setNeedsDisplayInRect方法重新繪制視圖的子區(qū)域,而視圖的其他部分依然保持不變。 一般情況下,你不應(yīng)該過(guò)早的進(jìn)行優(yōu)化。繪圖代碼可能看上去非常的繁瑣,但它們是非常快的。并且iOS繪圖系統(tǒng)自身也是非常高效,它不會(huì)頻繁調(diào)用drawRect:方法,除非迫不得已(或調(diào)用了setNeedsDisplay方法)。一旦一個(gè)視圖已由自己繪制完成,那么繪制的結(jié)果會(huì)被緩存下來(lái)留待重用,而不是每次重頭再來(lái)。(蘋(píng)果公司將緩存繪圖稱為視圖的位圖存儲(chǔ)回填(bitmap backing store))。你可能會(huì)發(fā)現(xiàn)drawRect:方法中的代碼在整個(gè)應(yīng)用程序生命周期內(nèi)只被調(diào)用了一次!事實(shí)上,將代碼移到drawRect:方法中是提高性能的普遍做法。這是因?yàn)槔L圖引擎直接對(duì)屏幕進(jìn)行渲染相對(duì)于先是脫屏渲染然后再將像素拷貝到屏幕要來(lái)的高效。 當(dāng)視圖的backgroundColor為nil并且opaque屬性為YES,視圖的背景顏色就會(huì)變成黑色。 Core Graphics上下文屬性設(shè)置當(dāng)你在圖形上下文中繪圖時(shí),當(dāng)前圖形上下文的相關(guān)屬性設(shè)置將決定繪圖的行為與外觀。因此,繪圖的一般過(guò)程是先設(shè)定好圖形上下文參數(shù),然后繪圖。比方說(shuō),要畫(huà)一根紅線,接著畫(huà)一根藍(lán)線。那么首先需要將上下文的線條顏色屬性設(shè)定為為紅色,然后畫(huà)紅線;接著設(shè)置上下文的線條顏色屬性為藍(lán)色,再畫(huà)出藍(lán)線。表面上看,紅線和藍(lán)線是分開(kāi)的,但事實(shí)上,在你畫(huà)每一條線時(shí),線條顏色卻是整個(gè)上下文的屬性。無(wú)論你用的是UIKit方法還是Core Graphics函數(shù)。 因?yàn)閳D形上下文在每一時(shí)刻都有一個(gè)確定的狀態(tài),該狀態(tài)概括了圖形上下文所有屬性的設(shè)置。為了便于操作這些狀態(tài),圖形上下文提供了一個(gè)用來(lái)持有狀態(tài)的棧。調(diào)用CGContextSaveGState函數(shù),上下文會(huì)將完整的當(dāng)前狀態(tài)壓入棧頂;調(diào)用CGContextRestoreGState函數(shù),上下文查找處在棧頂?shù)臓顟B(tài),并設(shè)置當(dāng)前上下文狀態(tài)為棧頂狀態(tài)。 因此一般繪圖模式是:在繪圖之前調(diào)用CGContextSaveGState函數(shù)保存當(dāng)前狀態(tài),接著根據(jù)需要設(shè)置某些上下文狀態(tài),然后繪圖,最后調(diào)用CGContextRestoreGState函數(shù)將當(dāng)前狀態(tài)恢復(fù)到繪圖之前的狀態(tài)。要注意的是,CGContextSaveGState函數(shù)和CGContextRestoreGState函數(shù)必須成對(duì)出現(xiàn),否則繪圖很可能出現(xiàn)意想不到的錯(cuò)誤,這里有一個(gè)簡(jiǎn)單的做法避免這種情況。代碼如下:
圖4 圖片合成快照 這個(gè)例子可能沒(méi)有什么吸引人的地方,因?yàn)樗幸磺卸伎梢允褂肅ore Graphics完成。除了Core Image是使用GPU處理,可能有點(diǎn)吸引人。Core Graphics也可以做到徑向漸變并使用混合模式合成圖片。但Core Image要簡(jiǎn)單得多,特別是當(dāng)你有多個(gè)圖片輸入想重用一個(gè)濾鏡鏈時(shí)。并且Core Image的顏色調(diào)整功能比Core Graphics更加強(qiáng)大。對(duì)了,Core Image還能實(shí)現(xiàn)自動(dòng)人臉識(shí)別哦! 繪制一個(gè)UIView繪制一個(gè)UIVIew最靈活的方式就是由它自己完成繪制。實(shí)際上你不是繪制一個(gè)UIView,你只是子類化了UIView并賦予子類繪制自己的能力。當(dāng)一個(gè)UIVIew需要執(zhí)行繪圖操作的時(shí), drawRect:方法就會(huì)被調(diào)用。覆蓋此方法讓你獲得繪圖操作的機(jī)會(huì)。當(dāng)drawRect:方法被調(diào)用,當(dāng)前圖形上下文也被設(shè)置為屬于視圖的圖形上下文。你可以使用Core Graphics或UIKit提供的方法將圖形畫(huà)到該上下文中。 你不應(yīng)該手動(dòng)調(diào)用drawRect:方法!如果你想調(diào)用drawRect:方法更新視圖,只需發(fā)送setNeedsDisplay方法。這將使得drawRect:方法會(huì)在下一個(gè)適當(dāng)?shù)臅r(shí)間調(diào)用。當(dāng)然,不要覆蓋drawRect:方法除非你知道這樣做絕對(duì)合法。比方說(shuō),在UIImageView子類中覆蓋drawRect:方法是不合法的,你將得不到你繪制的圖形。 在UIView子類的drawRect:方法中無(wú)需調(diào)用super,因?yàn)楸旧鞺IView的drawRect:方法是空的。為了提高一些繪圖性能,你可以調(diào)用setNeedsDisplayInRect方法重新繪制視圖的子區(qū)域,而視圖的其他部分依然保持不變。 一般情況下,你不應(yīng)該過(guò)早的進(jìn)行優(yōu)化。繪圖代碼可能看上去非常的繁瑣,但它們是非常快的。并且iOS繪圖系統(tǒng)自身也是非常高效,它不會(huì)頻繁調(diào)用drawRect:方法,除非迫不得已(或調(diào)用了setNeedsDisplay方法)。一旦一個(gè)視圖已由自己繪制完成,那么繪制的結(jié)果會(huì)被緩存下來(lái)留待重用,而不是每次重頭再來(lái)。(蘋(píng)果公司將緩存繪圖稱為視圖的位圖存儲(chǔ)回填(bitmap backing store))。你可能會(huì)發(fā)現(xiàn)drawRect:方法中的代碼在整個(gè)應(yīng)用程序生命周期內(nèi)只被調(diào)用了一次!事實(shí)上,將代碼移到drawRect:方法中是提高性能的普遍做法。這是因?yàn)槔L圖引擎直接對(duì)屏幕進(jìn)行渲染相對(duì)于先是脫屏渲染然后再將像素拷貝到屏幕要來(lái)的高效。 當(dāng)視圖的backgroundColor為nil并且opaque屬性為YES,視圖的背景顏色就會(huì)變成黑色。 Core Graphics上下文屬性設(shè)置當(dāng)你在圖形上下文中繪圖時(shí),當(dāng)前圖形上下文的相關(guān)屬性設(shè)置將決定繪圖的行為與外觀。因此,繪圖的一般過(guò)程是先設(shè)定好圖形上下文參數(shù),然后繪圖。比方說(shuō),要畫(huà)一根紅線,接著畫(huà)一根藍(lán)線。那么首先需要將上下文的線條顏色屬性設(shè)定為為紅色,然后畫(huà)紅線;接著設(shè)置上下文的線條顏色屬性為藍(lán)色,再畫(huà)出藍(lán)線。表面上看,紅線和藍(lán)線是分開(kāi)的,但事實(shí)上,在你畫(huà)每一條線時(shí),線條顏色卻是整個(gè)上下文的屬性。無(wú)論你用的是UIKit方法還是Core Graphics函數(shù)。 因?yàn)閳D形上下文在每一時(shí)刻都有一個(gè)確定的狀態(tài),該狀態(tài)概括了圖形上下文所有屬性的設(shè)置。為了便于操作這些狀態(tài),圖形上下文提供了一個(gè)用來(lái)持有狀態(tài)的棧。調(diào)用CGContextSaveGState函數(shù),上下文會(huì)將完整的當(dāng)前狀態(tài)壓入棧頂;調(diào)用CGContextRestoreGState函數(shù),上下文查找處在棧頂?shù)臓顟B(tài),并設(shè)置當(dāng)前上下文狀態(tài)為棧頂狀態(tài)。 因此一般繪圖模式是:在繪圖之前調(diào)用CGContextSaveGState函數(shù)保存當(dāng)前狀態(tài),接著根據(jù)需要設(shè)置某些上下文狀態(tài),然后繪圖,最后調(diào)用CGContextRestoreGState函數(shù)將當(dāng)前狀態(tài)恢復(fù)到繪圖之前的狀態(tài)。要注意的是,CGContextSaveGState函數(shù)和CGContextRestoreGState函數(shù)必須成對(duì)出現(xiàn),否則繪圖很可能出現(xiàn)意想不到的錯(cuò)誤,這里有一個(gè)簡(jiǎn)單的做法避免這種情況。代碼如下: - (void)drawRect:(CGRect)rect {  CGContextRef ctx = UIGraphicsGetCurrentContext();  CGContextSaveGState(ctx);  {  // 繪圖代碼  }  CGContextRestoreGState(ctx);      }    但你不需要在每次修改上下文狀態(tài)之前都這樣做,因?yàn)槟銓?duì)某一上下文屬性的設(shè)置并不一定會(huì)和之前的屬性設(shè)置或其他的屬性設(shè)置產(chǎn)生沖突。你完全可以在不調(diào)用保存和恢復(fù)函數(shù)的情況下先設(shè)置線條顏色為紅色,然后再設(shè)置為藍(lán)色。但在一定情況下,你希望你對(duì)狀態(tài)的設(shè)置是可撤銷的,我將在接下來(lái)討論這樣的情況。 許多的屬性組成了一個(gè)圖形上下文狀態(tài),這些屬性設(shè)置決定了在你繪圖時(shí)圖形的外觀和行為。下面我列出了一些屬性和對(duì)應(yīng)修改屬性的函數(shù);雖然這些函數(shù)是關(guān)于Core Graphics的,但記住,實(shí)際上UIKit同樣是調(diào)用這些函數(shù)操縱上下文狀態(tài)。 線條的寬度和線條的虛線樣式CGContextSetLineWidth、CGContextSetLineDash 線帽和線條聯(lián)接點(diǎn)樣式CGContextSetLineCap、CGContextSetLineJoin、CGContextSetMiterLimit 線條顏色和線條模式CGContextSetRGBStrokeColor、CGContextSetGrayStrokeColor、CGContextSetStrokeColorWithColor、CGContextSetStrokePattern 填充顏色和模式CGContextSetRGBFillColor,CGContextSetGrayFillColor,CGContextSetFillColorWithColor, CGContextSetFillPattern 陰影CGContextSetShadow、CGContextSetShadowWithColor 混合模式CGContextSetBlendMode(決定你當(dāng)前繪制的圖形與已經(jīng)存在的圖形如何被合成) 整體透明度CGContextSetAlpha(個(gè)別顏色也具有alpha成分) 文本屬性CGContextSelectFont、CGContextSetFont、CGContextSetFontSize、CGContextSetTextDrawingMode、CGContextSetCharacterSpacing 是否開(kāi)啟反鋸齒和字體平滑CGContextSetShouldAntialias、CGContextSetShouldSmoothFonts 另外一些屬性設(shè)置: 裁剪區(qū)域:在裁剪區(qū)域外繪圖不會(huì)被實(shí)際的畫(huà)出來(lái)。 變換(或稱為“CTM“,意為當(dāng)前變換矩陣): 改變你隨后指定的繪圖命令中的點(diǎn)如何被映射到畫(huà)布的物理空間。 許多這些屬性設(shè)置接下來(lái)我都會(huì)舉例說(shuō)明。 路徑與繪圖通過(guò)編寫(xiě)移動(dòng)虛擬畫(huà)筆的代碼描畫(huà)一段路徑,這樣的路徑并不構(gòu)成一個(gè)圖形。繪制路徑意味著對(duì)路徑描邊或填充該路徑,也或者兩者都做。同樣,你應(yīng)該從某些繪圖程序中得到過(guò)相似的體會(huì)。 一段路徑是由點(diǎn)到點(diǎn)的描畫(huà)構(gòu)成。想象一下繪圖系統(tǒng)是你手里的一只畫(huà)筆,你首先必須要設(shè)置畫(huà)筆當(dāng)前所處的位置,然后給出一系列命令告訴畫(huà)筆如何描畫(huà)隨后的每段路徑。每一段新增的路徑開(kāi)始于當(dāng)前點(diǎn),當(dāng)完成一條路徑的描畫(huà),路徑的終點(diǎn)就變成了當(dāng)前點(diǎn)。 下面列出了一些路徑描畫(huà)的命令: 定位當(dāng)前點(diǎn)CGContextMoveToPoint 描畫(huà)一條線CGContextAddLineToPoint、CGContextAddLines 描畫(huà)一個(gè)矩形CGContextAddRect、CGContextAddRects 描畫(huà)一個(gè)橢圓或圓形CGContextAddEllipseInRect 描畫(huà)一段圓弧CGContextAddArcToPoint、CGContextAddArc 通過(guò)一到兩個(gè)控制點(diǎn)描畫(huà)一段貝賽爾曲線CGContextAddQuadCurveToPoint、CGContextAddCurveToPoint 關(guān)閉當(dāng)前路徑CGContextClosePath 這將從路徑的終點(diǎn)到起點(diǎn)追加一條線。如果你打算填充一段路徑,那么就不需要使用該命令,因?yàn)樵撁顣?huì)被自動(dòng)調(diào)用。 描邊或填充當(dāng)前路徑CGContextStrokePath、CGContextFillPath、CGContextEOFillPath、CGContextDrawPath。對(duì)當(dāng)前路徑描邊或填充會(huì)清除掉路徑。如果你只想使用一條命令完成描邊和填充任務(wù),可以使用CGContextDrawPath命令,因?yàn)槿绻阒皇鞘褂肅GContextStrokePath對(duì)路徑描邊,路徑就會(huì)被清除掉,你就不能再對(duì)它進(jìn)行填充了。 創(chuàng)建路徑并描邊路徑或填充路徑只需一條命令就可完成的函數(shù):CGContextStrokeLineSegments、CGContextStrokeRect、CGContextStrokeRectWithWidth、CGContextFillRect、CGContextFillRects、CGContextStrokeEllipseInRect、CGContextFillEllipseInRect。 一段路徑是被合成的,意思是它是由多條獨(dú)立的路徑組成。舉個(gè)例子,一條單獨(dú)的路徑可能由兩個(gè)獨(dú)立的閉合形狀組成:一個(gè)矩形和一個(gè)圓形。當(dāng)你在構(gòu)造一條路徑的中間過(guò)程(意思是在描畫(huà)了一條路徑后沒(méi)有調(diào)用描邊或填充命令,或調(diào)用CGContextBeginPath函數(shù)來(lái)清除路徑)調(diào)用CGContextMoveToPoint函數(shù),就像是你拾起畫(huà)筆,并將畫(huà)筆移動(dòng)到一個(gè)新的位置,如此來(lái)準(zhǔn)備開(kāi)始一段獨(dú)立的相同路徑。如果你擔(dān)心當(dāng)你開(kāi)始描畫(huà)一條路徑的時(shí)候,已經(jīng)存在的路徑和新的路徑會(huì)被認(rèn)為是已存在路徑的一個(gè)合成部分,你可以調(diào)用CGContextBeginPath函數(shù)指定你繪制的路徑是一條獨(dú)立的路徑;蘋(píng)果的許多例子都是這樣做的,但在實(shí)際開(kāi)發(fā)中我發(fā)現(xiàn)這是非必要的。 CGContextClearRect函數(shù)的功能是擦除一個(gè)區(qū)域。這個(gè)函數(shù)會(huì)擦除一個(gè)矩形內(nèi)的所有已存在的繪圖;并對(duì)該區(qū)域執(zhí)行裁剪。結(jié)果像是打了一個(gè)貫穿所有已存在繪圖的孔。 CGContextClearRect函數(shù)的行為依賴于上下文是透明還是不透明。當(dāng)在圖形上下文中繪圖時(shí),這會(huì)尤為明顯和直觀。如果圖片上下文是透明的(UIGraphicsBeginImageContextWithOptions第二個(gè)參數(shù)為NO),那么CGContextClearRect函數(shù)執(zhí)行擦除后的顏色為透明,反之則為黑色。 當(dāng)在一個(gè)視圖中直接繪圖(使用drawRect:或drawLayer:inContext:方法),如果視圖的背景顏色為nil或顏色哪怕有一點(diǎn)點(diǎn)透明度,那么CGContextClearRect的矩形區(qū)域?qū)?huì)顯示為透明的,打出的孔將穿過(guò)視圖包括它的背景顏色。如果背景顏色完全不透明,那么CGContextClearRect函數(shù)的結(jié)果將會(huì)是黑色。這是因?yàn)橐晥D的背景顏色決定了是否視圖的圖形上下文是透明的還是不透明的。 圖5 CGContextClearRect函數(shù)的應(yīng)用 如圖5,在左邊的藍(lán)色正方形被挖去部分留為黑色,然而在右邊的藍(lán)色正方形也被挖去部分留為透明。但這兩個(gè)正方形都是UIView子類的實(shí)例,采用相同的繪圖代碼!不同之處在于視圖的背景顏色,左邊的正方形的背景顏色在nib文件中 但是這卻完全改變了CGContextClearRect函數(shù)的效果。UIView子類的drawRect:方法看起來(lái)像這樣:
圖5 CGContextClearRect函數(shù)的應(yīng)用 如圖5,在左邊的藍(lán)色正方形被挖去部分留為黑色,然而在右邊的藍(lán)色正方形也被挖去部分留為透明。但這兩個(gè)正方形都是UIView子類的實(shí)例,采用相同的繪圖代碼!不同之處在于視圖的背景顏色,左邊的正方形的背景顏色在nib文件中 但是這卻完全改變了CGContextClearRect函數(shù)的效果。UIView子類的drawRect:方法看起來(lái)像這樣: CGContextRef con = UIGraphicsGetCurrentContext();  CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);  CGContextFillRect(con, rect);  CGContextClearRect(con, CGRectMake(0,0,30,30));  為了說(shuō)明典型路徑的描畫(huà)命令,我將生成一個(gè)向上的箭頭圖案,我謹(jǐn)慎避免使用便利函數(shù)操作,也許這不是創(chuàng)建箭頭最好的方式,但依然清楚的展示了各種典型命令的用法。 圖6 一個(gè)簡(jiǎn)單的路徑繪圖
圖6 一個(gè)簡(jiǎn)單的路徑繪圖  CGContextRef con = UIGraphicsGetCurrentContext();  // 繪制一個(gè)黑色的垂直黑色線,作為箭頭的桿子  CGContextMoveToPoint(con, 100, 100);  CGContextAddLineToPoint(con, 100, 19);  CGContextSetLineWidth(con, 20);  CGContextStrokePath(con);  // 繪制一個(gè)紅色三角形箭頭  CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);  CGContextMoveToPoint(con, 80, 25);  CGContextAddLineToPoint(con, 100, 0);  CGContextAddLineToPoint(con, 120, 25);  CGContextFillPath(con);  // 從箭頭桿子上裁掉一個(gè)三角形,使用清除混合模式  CGContextMoveToPoint(con, 90, 101);  CGContextAddLineToPoint(con, 100, 90);  CGContextAddLineToPoint(con, 110, 101);  CGContextSetBlendMode(con, kCGBlendModeClear);  CGContextFillPath(con);   確切的說(shuō),為了以防萬(wàn)一,我們應(yīng)該在繪圖代碼周圍使用CGContextSaveGState和CGContextRestoreGState函數(shù)。可對(duì)于這個(gè)例子來(lái)說(shuō),添加與否不會(huì)有任何的區(qū)別。因?yàn)樯舷挛脑谡{(diào)用drawRect:方法中不會(huì)被持久,所以不會(huì)被破壞。 如果一段路徑需要重用或共享,你可以將路徑封裝為CGPath(具體類型是CGPathRef)。你可以創(chuàng)建一個(gè)新的CGMutablePathRef對(duì)象并使用多個(gè)類似于圖形的路徑函數(shù)的CGPath函數(shù)構(gòu)造路徑,或者使用CGContextCopyPath函數(shù)復(fù)制圖形上下文的當(dāng)前路徑。有許多CGPath函數(shù)可用于創(chuàng)建基于簡(jiǎn)單幾何形狀的路徑(CGPathCreateWithRect、CGPathCreateWithEllipseInRect)或基于已存在路徑(CGPathCreateCopyByStrokingPath、CGPathCreateCopyDashingPath、CGPathCreateCopyByTransformingPath)。 UIKit的UIBezierPath類包裝了CGPath。它提供了用于繪制某種形狀路徑的方法,以及用于描邊、填充、存取某些當(dāng)前上下文狀態(tài)的設(shè)置方法。類似地,UIColor提供了用于設(shè)置當(dāng)前上下文描邊與填充的顏色。因此我們可以重寫(xiě)我們之前繪制箭頭的代碼: UIBezierPath* p = [UIBezierPath bezierPath];  [p moveToPoint:CGPointMake(100,100)];  [p addLineToPoint:CGPointMake(100, 19)];  [p setLineWidth:20];  [p stroke];  [[UIColor redColor] set];  [p removeAllPoints];  [p moveToPoint:CGPointMake(80,25)];  [p addLineToPoint:CGPointMake(100, 0)];  [p addLineToPoint:CGPointMake(120, 25)];  [p fill];  [p removeAllPoints];  [p moveToPoint:CGPointMake(90,101)];  [p addLineToPoint:CGPointMake(100, 90)];  [p addLineToPoint:CGPointMake(110, 101)];  [p fillWithBlendMode:kCGBlendModeClear alpha:1.0];  在這種特殊情況下,完成同樣的工作并沒(méi)有節(jié)省多少代碼,但是UIBezierPath仍然還是有用的。如果你需要對(duì)象特性,UIBezierPath提供了一個(gè)便利方法:bezierPathWithRoundedRect:cornerRadius:,它可用于繪制帶有圓角的矩形,如果是使用Core Graphics就相當(dāng)冗長(zhǎng)乏味了。還可以只讓圓角出現(xiàn)在左上角和右上角。- (void)drawRect:(CGRect)rect {    CGContextRef ctx = UIGraphicsGetCurrentContext();    CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);    CGContextSetLineWidth(ctx, 3);    UIBezierPath *path;    path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 100, 100) byRoundingCorners:(UIRectCornerTopLeft |UIRectCornerToPRight) cornerRadii:CGSizeMake(10, 10)];    [path stroke];  }    圖7 左右圓角矩形 裁剪路徑的另一用處是遮蔽區(qū)域,以防對(duì)遮蔽區(qū)域進(jìn)一步繪圖。這種用法被稱為裁剪。裁剪區(qū)域外的圖形不會(huì)被繪制到。默認(rèn)情況下,一個(gè)圖形上下文的裁剪區(qū)域是整個(gè)圖形上下文。你可在上下文中的任何地方繪圖。 總的來(lái)說(shuō),裁剪區(qū)域是上下文的一個(gè)特性。與已存在的裁剪區(qū)域相交會(huì)出現(xiàn)新的裁剪區(qū)域。所以如果你應(yīng)用了你自己的裁剪區(qū)域,稍后將它從圖形上下文中移除的做法是使用CGContextSaveGState和CGContextRestoreGState函數(shù)將代碼包裝起來(lái)。 為了便于說(shuō)明這一點(diǎn),我使用裁剪而不是使用混合模式在箭頭桿子上打孔的方法重寫(xiě)了生成箭頭的代碼。這樣做有點(diǎn)小復(fù)雜,因?yàn)槲覀兿胍眉魠^(qū)域不在三角形內(nèi)而在三角形外部。為了表明這一點(diǎn),我們使用了一個(gè)三角形和一個(gè)矩形組成了一個(gè)組合路徑。 當(dāng)填充一個(gè)組合路徑并使用它表示一個(gè)裁剪區(qū)域時(shí),系統(tǒng)遵循以下兩規(guī)則之一: 環(huán)繞規(guī)則(Winding rule)如果邊界是順時(shí)針繪制,那么在其內(nèi)部逆時(shí)針繪制的邊界所包含的內(nèi)容為空。如果邊界是逆時(shí)針繪制,那么在其內(nèi)部順時(shí)針繪制的邊界所包含的內(nèi)容為空。 奇偶規(guī)則最外層的邊界代表內(nèi)部都有效,都要填充;之后向內(nèi)第二個(gè)邊界代表它的內(nèi)部無(wú)效,不需填充;如此規(guī)則繼續(xù)向內(nèi)尋找邊界線。我們的情況非常簡(jiǎn)單,所以使用奇偶規(guī)則就很容易了。這里我們使用CGContextEOCllip設(shè)置裁剪區(qū)域然后進(jìn)行繪圖。(如果不是很明白,可以參見(jiàn)這篇文章:五種方法繪制有孔的2d形狀)
圖7 左右圓角矩形 裁剪路徑的另一用處是遮蔽區(qū)域,以防對(duì)遮蔽區(qū)域進(jìn)一步繪圖。這種用法被稱為裁剪。裁剪區(qū)域外的圖形不會(huì)被繪制到。默認(rèn)情況下,一個(gè)圖形上下文的裁剪區(qū)域是整個(gè)圖形上下文。你可在上下文中的任何地方繪圖。 總的來(lái)說(shuō),裁剪區(qū)域是上下文的一個(gè)特性。與已存在的裁剪區(qū)域相交會(huì)出現(xiàn)新的裁剪區(qū)域。所以如果你應(yīng)用了你自己的裁剪區(qū)域,稍后將它從圖形上下文中移除的做法是使用CGContextSaveGState和CGContextRestoreGState函數(shù)將代碼包裝起來(lái)。 為了便于說(shuō)明這一點(diǎn),我使用裁剪而不是使用混合模式在箭頭桿子上打孔的方法重寫(xiě)了生成箭頭的代碼。這樣做有點(diǎn)小復(fù)雜,因?yàn)槲覀兿胍眉魠^(qū)域不在三角形內(nèi)而在三角形外部。為了表明這一點(diǎn),我們使用了一個(gè)三角形和一個(gè)矩形組成了一個(gè)組合路徑。 當(dāng)填充一個(gè)組合路徑并使用它表示一個(gè)裁剪區(qū)域時(shí),系統(tǒng)遵循以下兩規(guī)則之一: 環(huán)繞規(guī)則(Winding rule)如果邊界是順時(shí)針繪制,那么在其內(nèi)部逆時(shí)針繪制的邊界所包含的內(nèi)容為空。如果邊界是逆時(shí)針繪制,那么在其內(nèi)部順時(shí)針繪制的邊界所包含的內(nèi)容為空。 奇偶規(guī)則最外層的邊界代表內(nèi)部都有效,都要填充;之后向內(nèi)第二個(gè)邊界代表它的內(nèi)部無(wú)效,不需填充;如此規(guī)則繼續(xù)向內(nèi)尋找邊界線。我們的情況非常簡(jiǎn)單,所以使用奇偶規(guī)則就很容易了。這里我們使用CGContextEOCllip設(shè)置裁剪區(qū)域然后進(jìn)行繪圖。(如果不是很明白,可以參見(jiàn)這篇文章:五種方法繪制有孔的2d形狀) CGContextRef con = UIGraphicsGetCurrentContext();  // 在上下文裁剪區(qū)域中挖一個(gè)三角形狀的孔  CGContextMoveToPoint(con, 90, 100);  CGContextAddLineToPoint(con, 100, 90);  CGContextAddLineToPoint(con, 110, 100);  CGContextClosePath(con);  CGContextAddRect(con, CGContextGetClipBoundingBox(con));  // 使用奇偶規(guī)則,裁剪區(qū)域?yàn)榫匦螠p去三角形區(qū)域  CGContextEOClip(con);  // 繪制垂線  CGContextMoveToPoint(con, 100, 100);  CGContextAddLineToPoint(con, 100, 19);  CGContextSetLineWidth(con, 20);  CGContextStrokePath(con);  // 畫(huà)紅色箭頭  CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);  CGContextMoveToPoint(con, 80, 25);  CGContextAddLineToPoint(con, 100, 0);  CGContextAddLineToPoint(con, 120, 25);  CGContextFillPath(con);    漸變漸變可以很簡(jiǎn)單也可以很復(fù)雜。一個(gè)簡(jiǎn)單的漸變(接下來(lái)要討論的)由一端點(diǎn)的顏色與另一端點(diǎn)的顏色決定,如果在中間點(diǎn)加入顏色(可選),那么漸變會(huì)在上下文的兩個(gè)點(diǎn)之間線性的繪制或在上下文的兩個(gè)圓之間放射狀的繪制。不能使用漸變作為路徑的填充色,但可使用裁剪限制對(duì)路徑形狀的漸變。 我重寫(xiě)了繪制箭頭的代碼,箭桿使用了線性漸變。效果如圖7所示。 圖8 箭頭桿子漸變
圖8 箭頭桿子漸變  CGContextRef con = UIGraphicsGetCurrentContext();  CGContextSaveGState(con);  // 在上下文裁剪區(qū)域挖一個(gè)三角形孔  CGContextMoveToPoint(con, 90, 100);  CGContextAddLineToPoint(con, 100, 90);  CGContextAddLineToPoint(con, 110, 100);  CGContextClosePath(con);  CGContextAddRect(con, CGContextGetClipBoundingBox(con));  CGContextEOClip(con);  //繪制一個(gè)垂線,讓它的輪廓形狀成為裁剪區(qū)域  CGContextMoveToPoint(con, 100, 100);  CGContextAddLineToPoint(con, 100, 19);  CGContextSetLineWidth(con, 20);  // 使用路徑的描邊版本替換圖形上下文的路徑  CGContextReplacePathWithStrokedPath(con);  // 對(duì)路徑的描邊版本實(shí)施裁剪  CGContextClip(con);  // 繪制漸變  CGFloat locs[3] = { 0.0, 0.5, 1.0 };  CGFloat colors[12] = {  0.3,0.3,0.3,0.8, // 開(kāi)始顏色,透明灰  0.0,0.0,0.0,1.0, // 中間顏色,黑色  0.3,0.3,0.3,0.8 // 末尾顏色,透明灰  };  CGColorSpaceRef sp = CGColorSpaceCreateDeviceGray();  CGGradientRef grad = CGGradientCreateWithColorComponents (sp, colors, locs, 3);  CGContextDrawLinearGradient(con, grad, CGPointMake(89,0), CGPointMake(111,0), 0);  CGColorSpaceRelease(sp);  CGGradientRelease(grad);  CGContextRestoreGState(con); // 完成裁剪  // 繪制紅色箭頭  CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);  CGContextMoveToPoint(con, 80, 25);  CGContextAddLineToPoint(con, 100, 0);  CGContextAddLineToPoint(con, 120, 25);  CGContextFillPath(con);  調(diào)用CGContextReplacePathWithStrokedPath函數(shù)假裝對(duì)當(dāng)前路徑描邊,并使用當(dāng)前線段寬度和與線段相關(guān)的上下文狀態(tài)設(shè)置。但接著創(chuàng)建的是描邊路徑外部的一個(gè)新的路徑。因此,相對(duì)于使用粗的線條,我們使用了一個(gè)矩形區(qū)域作為裁剪區(qū)域。 雖然過(guò)程比較冗長(zhǎng)但是非常的簡(jiǎn)單;我們將漸變描述為一組在一端點(diǎn)(0.0)和另一端點(diǎn)(1.0)之間連續(xù)區(qū)上的位置,以及設(shè)置與每個(gè)位置相對(duì)應(yīng)的顏色。為了提亮邊緣的漸變,加深中間的漸變,我使用了三個(gè)位置,黑色點(diǎn)的位置是0.5。為了創(chuàng)建漸變,還需要提供一個(gè)顏色空間。最后,我創(chuàng)建出了該漸變,并對(duì)裁剪區(qū)域繪制線性漸變,最后釋放了顏色空間和漸變。 顏色與模板在iOS中,CGColor表示顏色(具體類型為CGColorRef)。使用UIColor的colorWithCGColor:和CGColor方法可bridged cast到UIColor。 在iOS中,模板表示為CGPattern(具體類型為CGPatternRef)。你可以創(chuàng)建一個(gè)模板并使用它進(jìn)行描邊或填充。其過(guò)程是相當(dāng)復(fù)雜的。作為一個(gè)非常簡(jiǎn)單的例子,我將使用紅藍(lán)相間的三角形替換箭頭的三角形部分。現(xiàn)在移除下面行:CGContextSetFillColorWithColor(con, [UIColor redColor].CGColor)); 在被移除的地方填入下面代碼: CGColorSpaceRef sp2 = CGColorSpaceCreatePattern(NULL);  CGContextSetFillColorSpace (con, sp2);  CGColorSpaceRelease (sp2);  CGPatternCallbacks callback = {0, &drawStripes, NULL };  CGAffineTransform tr = CGAffineTransformIdentity;  CGPatternRef patt = CGPatternCreate(NULL,CGRectMake(0,0,4,4), tr, 4, 4, kCGPatternTilingConstantSpacingMinimalDistortion, true, &callback);  CGFloat alph = 1.0;  CGContextSetFillPattern(con, patt, &alph);  CGPatternRelease(patt);  代碼非常冗長(zhǎng),但它卻是一個(gè)完整的樣板。現(xiàn)在我們從后往前分析代碼: 我們調(diào)用CGContextSetFillPattern不是設(shè)置填充顏色,我們?cè)O(shè)置的是填充的模板。函數(shù)的第三個(gè)參數(shù)是一個(gè)指向CGFloat的指針,所以我們事先設(shè)置CGFloat自身。第二個(gè)參數(shù)是一個(gè)CGPatternRef對(duì)象,所以我們需要事先創(chuàng)建CGPatternRef,并在最后釋放它。 現(xiàn)在開(kāi)始討論CGPatternCreate。一個(gè)模板是在一個(gè)矩形元中的繪圖。我們需要矩形元的尺寸(第二個(gè)參數(shù))以及矩形元原始點(diǎn)之間的間隙(第四和第五個(gè)參數(shù))。這這種情況下,矩形元是4*4的,每一個(gè)矩形元與它的周圍矩形元是緊密貼合的。我們需要提供一個(gè)應(yīng)用到矩形元的變換參數(shù)(第三個(gè)參數(shù));在這種情況下,我們不需要變換做什么工作,所以我們應(yīng)用了一個(gè)恒等變換。我們應(yīng)用了一個(gè)瓷磚規(guī)則(第六個(gè)參數(shù))。我們需要聲明的是顏色模板不是漏印(stencil)模板,所以參數(shù)值為true。并且我們需要提供一個(gè)指向回調(diào)函數(shù)的指針,回調(diào)函數(shù)的工作是向矩形元繪制模板。第八個(gè)參數(shù)是一個(gè)指向CGPatternCallbacks結(jié)構(gòu)體的指針。這個(gè)結(jié)構(gòu)體由數(shù)字0和兩個(gè)指向函數(shù)的指針構(gòu)成。第一個(gè)函數(shù)指針指向的函數(shù)當(dāng)模板被繪制到矩形元中被調(diào)用,第二個(gè)函數(shù)指針指向的函數(shù)當(dāng)模板被釋放后調(diào)用。第二個(gè)函數(shù)指針我們沒(méi)有指定,它的存在主要是為了內(nèi)存管理的需要。但在這個(gè)簡(jiǎn)單的例子中,我們并不需要。 在你使用顏色模板調(diào)用CGContextSetFillPattern函數(shù)之前,你需要設(shè)置將應(yīng)用到模板顏色空間的上下文填充顏色空間。如果你忽略這項(xiàng)工作,那么當(dāng)你調(diào)用CGContextSetFillPattern函數(shù)時(shí)會(huì)發(fā)生錯(cuò)誤。所以我們創(chuàng)建了顏色空間,設(shè)置它作為上下文的填充顏色空間,并在后面做了釋放。 到這里我們?nèi)匀粵](méi)有完成繪圖。因?yàn)槲疫€沒(méi)有編寫(xiě)向矩形元中繪圖的函數(shù)!繪圖函數(shù)地址被表示為&drawStripes。繪圖代碼如下所示: void drawStripes (void *info, CGContextRef con) {  // assume 4 x 4 cell  CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);  CGContextFillRect(con, CGRectMake(0,0,4,4));  CGContextSetFillColorWithColor(con, [[UIColor blueColor] CGColor]);  CGContextFillRect(con, CGRectMake(0,0,4,2));  }    圖9 模板填充 如你所見(jiàn),實(shí)際的模板繪圖代碼是非常簡(jiǎn)單的。唯一的復(fù)雜點(diǎn)在于CGPatternCreate函數(shù)必須與模板繪圖函數(shù)的矩形元尺寸相同。我們知道矩形元的尺寸為4*4,所以我們用紅色填充它,并接著填充它的下半部分為綠色。當(dāng)這些矩形元被水平垂直平鋪時(shí),我們得到了如圖8所示的條紋圖案。 注意,最后圖形上下文遺留下了一個(gè)不可取的狀態(tài),即填充顏色空間被設(shè)置為了一個(gè)模板顏色空間。如果稍后嘗試設(shè)置填充顏色為常規(guī)顏色,就會(huì)引起錯(cuò)誤。通常的解決方案是,使用CGContextSaveGState和CGContextRestoreGState函數(shù)將代碼包起來(lái)。 你可能觀察到圖8的平鋪效果并不與箭頭的三角形內(nèi)部相符合:最底部的似乎只平鋪了一半藍(lán)色。這是因?yàn)橐粋€(gè)模板的定位并不關(guān)心你填充(描邊)的形狀,總的來(lái)說(shuō)它只關(guān)心圖形上下文。我們可以調(diào)用CGContextSetPatternPhase函數(shù)改變模板的定位。 圖形上下文變換就像UIView可以實(shí)現(xiàn)變換,同樣圖形上下文也具備這項(xiàng)功能。然而對(duì)圖形上下文應(yīng)用一個(gè)變換操作不會(huì)對(duì)已在圖形上下文上的繪圖產(chǎn)生什么影響,它只會(huì)影響到在上下文變換之后被繪制的圖形,并改變被映射到圖形上下文區(qū)域的坐標(biāo)方式。一個(gè)圖形上下文變換被稱為CTM,意為“當(dāng)前變換矩陣“(current transformation matrix)。 完全利用圖形上下文的CTM來(lái)免于即使是簡(jiǎn)單的計(jì)算操作是很常見(jiàn)的。你可以使用CGContextConcatCTM函數(shù)將當(dāng)前變換乘上任何CGAffineTransform,還有一些便利函數(shù)可對(duì)當(dāng)前變換應(yīng)用平移、縮放,旋轉(zhuǎn)變換。 當(dāng)你獲得上下文的時(shí)候,對(duì)圖形上下文的基本變換已經(jīng)設(shè)置好了;這就是系統(tǒng)能映射上下文繪圖坐標(biāo)到屏幕坐標(biāo)的原因。無(wú)論你對(duì)當(dāng)前變換應(yīng)用了什么變換,基本變換變換依然有效并且繪圖繼續(xù)工作。通過(guò)將你的變換代碼封裝到CGContextSaveGState和CGContextRestoreGState函數(shù)調(diào)用中,對(duì)基本變換應(yīng)用的變換操作可以被還原。 舉個(gè)例子,對(duì)于我們迄今為止使用代碼繪制的向上箭頭來(lái)說(shuō),已知的放置箭頭的方式僅僅只有一個(gè)位置:箭頭矩形框的左上角被硬編碼在坐標(biāo){80,0}。這樣代碼很難理解、靈活性差、且很難被重用。最明智的做法是通過(guò)將所有代碼中的x坐標(biāo)值減去80,讓箭頭矩形框左上角在坐標(biāo){0,0}。事先應(yīng)用一個(gè)簡(jiǎn)單的平移變換,很容易將箭頭畫(huà)在任何位置。為了映射坐標(biāo)到箭頭的左上角,我們使用下面代碼: CGContextTranslateCTM(con, 80, 0); //在坐標(biāo){0,0}處繪制箭頭 旋轉(zhuǎn)變換特別的有用,它可以讓你在一個(gè)被旋轉(zhuǎn)的方向上進(jìn)行繪制而無(wú)需使用任何復(fù)雜的三角函數(shù)。然而這略有點(diǎn)復(fù)雜,因?yàn)樾D(zhuǎn)變換圍繞的點(diǎn)是原點(diǎn)坐標(biāo)。這幾乎不是你所想要的,所以你先是應(yīng)用了一個(gè)平移變換,為的是映射原點(diǎn)到你真正想繞其旋轉(zhuǎn)的點(diǎn)。但是接著,在旋轉(zhuǎn)之后,為了算出你在哪里繪圖,你可能需要做一次逆向平移變換。為了說(shuō)明這個(gè)做法,我將繞箭頭桿子尾部旋轉(zhuǎn)多個(gè)角度重復(fù)繪制箭頭,并把對(duì)箭頭的繪圖封裝為UIImage對(duì)象。接著我們簡(jiǎn)單重復(fù)繪制UIImage對(duì)象。具體代碼如下:
圖9 模板填充 如你所見(jiàn),實(shí)際的模板繪圖代碼是非常簡(jiǎn)單的。唯一的復(fù)雜點(diǎn)在于CGPatternCreate函數(shù)必須與模板繪圖函數(shù)的矩形元尺寸相同。我們知道矩形元的尺寸為4*4,所以我們用紅色填充它,并接著填充它的下半部分為綠色。當(dāng)這些矩形元被水平垂直平鋪時(shí),我們得到了如圖8所示的條紋圖案。 注意,最后圖形上下文遺留下了一個(gè)不可取的狀態(tài),即填充顏色空間被設(shè)置為了一個(gè)模板顏色空間。如果稍后嘗試設(shè)置填充顏色為常規(guī)顏色,就會(huì)引起錯(cuò)誤。通常的解決方案是,使用CGContextSaveGState和CGContextRestoreGState函數(shù)將代碼包起來(lái)。 你可能觀察到圖8的平鋪效果并不與箭頭的三角形內(nèi)部相符合:最底部的似乎只平鋪了一半藍(lán)色。這是因?yàn)橐粋€(gè)模板的定位并不關(guān)心你填充(描邊)的形狀,總的來(lái)說(shuō)它只關(guān)心圖形上下文。我們可以調(diào)用CGContextSetPatternPhase函數(shù)改變模板的定位。 圖形上下文變換就像UIView可以實(shí)現(xiàn)變換,同樣圖形上下文也具備這項(xiàng)功能。然而對(duì)圖形上下文應(yīng)用一個(gè)變換操作不會(huì)對(duì)已在圖形上下文上的繪圖產(chǎn)生什么影響,它只會(huì)影響到在上下文變換之后被繪制的圖形,并改變被映射到圖形上下文區(qū)域的坐標(biāo)方式。一個(gè)圖形上下文變換被稱為CTM,意為“當(dāng)前變換矩陣“(current transformation matrix)。 完全利用圖形上下文的CTM來(lái)免于即使是簡(jiǎn)單的計(jì)算操作是很常見(jiàn)的。你可以使用CGContextConcatCTM函數(shù)將當(dāng)前變換乘上任何CGAffineTransform,還有一些便利函數(shù)可對(duì)當(dāng)前變換應(yīng)用平移、縮放,旋轉(zhuǎn)變換。 當(dāng)你獲得上下文的時(shí)候,對(duì)圖形上下文的基本變換已經(jīng)設(shè)置好了;這就是系統(tǒng)能映射上下文繪圖坐標(biāo)到屏幕坐標(biāo)的原因。無(wú)論你對(duì)當(dāng)前變換應(yīng)用了什么變換,基本變換變換依然有效并且繪圖繼續(xù)工作。通過(guò)將你的變換代碼封裝到CGContextSaveGState和CGContextRestoreGState函數(shù)調(diào)用中,對(duì)基本變換應(yīng)用的變換操作可以被還原。 舉個(gè)例子,對(duì)于我們迄今為止使用代碼繪制的向上箭頭來(lái)說(shuō),已知的放置箭頭的方式僅僅只有一個(gè)位置:箭頭矩形框的左上角被硬編碼在坐標(biāo){80,0}。這樣代碼很難理解、靈活性差、且很難被重用。最明智的做法是通過(guò)將所有代碼中的x坐標(biāo)值減去80,讓箭頭矩形框左上角在坐標(biāo){0,0}。事先應(yīng)用一個(gè)簡(jiǎn)單的平移變換,很容易將箭頭畫(huà)在任何位置。為了映射坐標(biāo)到箭頭的左上角,我們使用下面代碼: CGContextTranslateCTM(con, 80, 0); //在坐標(biāo){0,0}處繪制箭頭 旋轉(zhuǎn)變換特別的有用,它可以讓你在一個(gè)被旋轉(zhuǎn)的方向上進(jìn)行繪制而無(wú)需使用任何復(fù)雜的三角函數(shù)。然而這略有點(diǎn)復(fù)雜,因?yàn)樾D(zhuǎn)變換圍繞的點(diǎn)是原點(diǎn)坐標(biāo)。這幾乎不是你所想要的,所以你先是應(yīng)用了一個(gè)平移變換,為的是映射原點(diǎn)到你真正想繞其旋轉(zhuǎn)的點(diǎn)。但是接著,在旋轉(zhuǎn)之后,為了算出你在哪里繪圖,你可能需要做一次逆向平移變換。為了說(shuō)明這個(gè)做法,我將繞箭頭桿子尾部旋轉(zhuǎn)多個(gè)角度重復(fù)繪制箭頭,并把對(duì)箭頭的繪圖封裝為UIImage對(duì)象。接著我們簡(jiǎn)單重復(fù)繪制UIImage對(duì)象。具體代碼如下:- (void)drawRect:(CGRect)rect {   UIGraphicsBeginImageContextWithOptions(CGSizeMake(40,100), NO, 0.0);  CGContextRef con = UIGraphicsGetCurrentContext();  CGContextSaveGState(con);  CGContextMoveToPoint(con, 90 - 80, 100);  CGContextAddLineToPoint(con, 100 - 80, 90);  CGContextAddLineToPoint(con, 110 - 80, 100);  CGContextMoveToPoint(con, 110 - 80, 100);  CGContextAddLineToPoint(con, 100 - 80, 90);  CGContextAddLineToPoint(con, 90 - 80, 100);  CGContextClosePath(con);  CGContextAddRect(con, CGContextGetClipBoundingBox(con));  CGContextEOClip(con);  CGContextMoveToPoint(con, 100 - 80, 100);  CGContextAddLineToPoint(con, 100 - 80, 19);  CGContextSetLineWidth(con, 20);  CGContextReplacePathWithStrokedPath(con);  CGContextClip(con);  CGFloat locs[3] = { 0.0, 0.5, 1.0 };  CGFloat colors[12] = {  0.3,0.3,0.3,0.8,  0.0,0.0,0.0,1.0,  0.3,0.3,0.3,0.8  };  CGColorSpaceRef sp = CGColorSpaceCreateDeviceGray();  CGGradientRef grad = CGGradientCreateWithColorComponents (sp, colors, locs, 3);  CGContextDrawLinearGradient (con, grad, CGPointMake(89 - 80,0), CGPointMake(111 - 80,0), 0);  CGColorSpaceRelease(sp);  CGGradientRelease(grad);  CGContextRestoreGState(con);  CGColorSpaceRef sp2 = CGColorSpaceCreatePattern(NULL);  CGContextSetFillColorSpace (con, sp2);  CGColorSpaceRelease (sp2);  CGPatternCallbacks callback = {0, &drawStripes, NULL };  CGAffineTransform tr = CGAffineTransformIdentity;  CGPatternRef patt = CGPatternCreate(NULL,CGRectMake(0,0,4,4),tr,4,4,kCGPatternTilingConstantSpacingMinimalDistortion,true, &callback);  CGFloat alph = 1.0;  CGContextSetFillPattern(con, patt, &alph);  CGPatternRelease(patt);  CGContextMoveToPoint(con, 80 - 80, 25);  CGContextAddLineToPoint(con, 100 - 80, 0);  CGContextAddLineToPoint(con, 120 - 80, 25);  CGContextFillPath(con);  UIImage* im = UIGraphicsGetImageFromCurrentImageContext();  UIGraphicsEndImageContext();  con = UIGraphicsGetCurrentContext();  [im drawAtPoint:CGPointMake(0,0)];  for (int i=0; i<3; i++) {  CGContextTranslateCTM(con, 20, 100);  CGContextRotateCTM(con, 30 * M_PI/180.0);  CGContextTranslateCTM(con, -20, -100);  [im drawAtPoint:CGPointMake(0,0)];  }  }   圖10 使用CTM旋轉(zhuǎn)變換變換有多個(gè)方法解決我們?cè)缙谑褂肅GContextDrawImage函數(shù)遇到的倒置問(wèn)題。相對(duì)于逆向繪圖,我們選擇逆向我們繪圖的上下文。實(shí)質(zhì)上,我們對(duì)上下文坐標(biāo)系統(tǒng)應(yīng)用了一個(gè)“倒置”變換。你自上而下移動(dòng)上下文,接著你通過(guò)應(yīng)用一個(gè)讓y坐標(biāo)乘以-1的縮放變換逆向y坐標(biāo)的方向。
圖10 使用CTM旋轉(zhuǎn)變換變換有多個(gè)方法解決我們?cè)缙谑褂肅GContextDrawImage函數(shù)遇到的倒置問(wèn)題。相對(duì)于逆向繪圖,我們選擇逆向我們繪圖的上下文。實(shí)質(zhì)上,我們對(duì)上下文坐標(biāo)系統(tǒng)應(yīng)用了一個(gè)“倒置”變換。你自上而下移動(dòng)上下文,接著你通過(guò)應(yīng)用一個(gè)讓y坐標(biāo)乘以-1的縮放變換逆向y坐標(biāo)的方向。CGContextTranslateCTM(con, 0, theHeight);  CGContextScaleCTM(con, 1.0, -1.0); 上下文的頂部應(yīng)該被你往下移動(dòng)多遠(yuǎn)依賴于你繪制的圖片。比如說(shuō)我們可以繪制沒(méi)有倒置問(wèn)題的兩個(gè)半邊的火星圖形(前面討論的一個(gè)例子)。CGContextTranslateCTM(con, 0, sz.height); // sz為[mars size]  CGContextScaleCTM(con, 1.0, -1.0);  CGContextDrawImage(con, CGRectMake(0, 0, sz.width/2.0, sz.height), marsLeft);  CGContextDrawImage(con, CGRectMake(b.size.width-sz.width/2.0, 0, sz.width/2.0, sz.height),marsRight);  陰影為了在繪圖上加入陰影,可在繪圖之前設(shè)置上下文的陰影值。陰影的位置表示為CGSize,如果CGSize的兩個(gè)值都是正數(shù),則表示陰影是朝下和朝右的。模糊度被表示為任何一個(gè)正數(shù)。蘋(píng)果沒(méi)有解釋縮放的工作方式,但實(shí)驗(yàn)表明12是最佳的模糊度,99及以上的模糊度會(huì)讓陰影變得不成形。 我在圖9的基礎(chǔ)上給上下文加了一個(gè)陰影:con = UIGraphicsGetCurrentContext();  CGContextSetShadow(con, CGSizeMake(7, 7), 12);  [im drawAtPoint:CGPointMake(0,0)];  然而,使用這種方法有一個(gè)不太明顯的問(wèn)題。我們是在每繪制一個(gè)箭頭的時(shí)候加上的陰影。因此,箭頭的陰影會(huì)投射在另一個(gè)箭頭上面。我們想要的是讓所有的箭頭集體地投射出一個(gè)陰影。解決方法是使用一個(gè)透明的圖層;該圖層類似一個(gè)先是疊加所有繪圖然后加上陰影的一個(gè)子上下文。代碼如下:con = UIGraphicsGetCurrentContext();  CGContextSetShadow(con, CGSizeMake(7, 7), 12);  CGContextBeginTransparencyLayer(con, NULL);  [im drawAtPoint:CGPointMake(0,0)];  for (int i=0; i<3; i++) {  CGContextTranslateCTM(con, 20, 100);  CGContextRotateCTM(con, 30 * M_PI/180.0);  CGContextTranslateCTM(con, -20, -100);  [im drawAtPoint:CGPointMake(0,0)];  }  // 在調(diào)用了CGContextEndTransparencyLayer函數(shù)之后,  // 圖層內(nèi)容會(huì)在應(yīng)用全局alpha和上下文陰影狀態(tài)之后被合成到上下文中  CGContextEndTransparencyLayer(con);     圖11 陰影效果點(diǎn)與像素一個(gè)點(diǎn)是由xy坐標(biāo)描述的一個(gè)無(wú)窮小量的位置。通過(guò)指定點(diǎn)實(shí)現(xiàn)在圖形上下文中的繪圖。我們并沒(méi)有關(guān)心設(shè)備的分辨率,因?yàn)镃ore Graphics已經(jīng)精細(xì)地將繪圖映射到物理輸出設(shè)備(基于CTM、反鋸齒和平滑技術(shù))。因此,文章之前的討論只關(guān)心圖形上下文的點(diǎn),不關(guān)注點(diǎn)與屏幕像素的關(guān)系。 然而像素是真實(shí)存在的。一個(gè)像素是真實(shí)世界中一個(gè)具有完整物理尺寸的顯示單元。整數(shù)的點(diǎn)實(shí)際上介于像素之間。在單分辨率設(shè)備上,這可能會(huì)讓人感到迷惑。比方說(shuō),如果使用線寬為1的線條對(duì)一個(gè)整數(shù)坐標(biāo)的垂直路徑描邊,那么線條將會(huì)被分為兩半,分別落在路徑的兩側(cè)。所以在單分辨率設(shè)備上線寬會(huì)變成2px(因?yàn)樵O(shè)備無(wú)法表示半個(gè)像素)。
圖11 陰影效果點(diǎn)與像素一個(gè)點(diǎn)是由xy坐標(biāo)描述的一個(gè)無(wú)窮小量的位置。通過(guò)指定點(diǎn)實(shí)現(xiàn)在圖形上下文中的繪圖。我們并沒(méi)有關(guān)心設(shè)備的分辨率,因?yàn)镃ore Graphics已經(jīng)精細(xì)地將繪圖映射到物理輸出設(shè)備(基于CTM、反鋸齒和平滑技術(shù))。因此,文章之前的討論只關(guān)心圖形上下文的點(diǎn),不關(guān)注點(diǎn)與屏幕像素的關(guān)系。 然而像素是真實(shí)存在的。一個(gè)像素是真實(shí)世界中一個(gè)具有完整物理尺寸的顯示單元。整數(shù)的點(diǎn)實(shí)際上介于像素之間。在單分辨率設(shè)備上,這可能會(huì)讓人感到迷惑。比方說(shuō),如果使用線寬為1的線條對(duì)一個(gè)整數(shù)坐標(biāo)的垂直路徑描邊,那么線條將會(huì)被分為兩半,分別落在路徑的兩側(cè)。所以在單分辨率設(shè)備上線寬會(huì)變成2px(因?yàn)樵O(shè)備無(wú)法表示半個(gè)像素)。 圖12 整數(shù)的點(diǎn)坐標(biāo)與偏移0.5點(diǎn)的坐標(biāo)對(duì)應(yīng)的描邊處理 當(dāng)你遇到顯示效果不佳的時(shí),可能會(huì)被建議通過(guò)對(duì)坐標(biāo)增減0.5讓它在像素中居中。這個(gè)建議可能有效,如圖11。但它只是做了一些頭腦簡(jiǎn)單的假設(shè)。一個(gè)復(fù)雜的做法是獲得UIView的contentScaleFactor屬性。這個(gè)值為1.0或2.0,所以你可以除以這個(gè)屬性值得到從像素到點(diǎn)的轉(zhuǎn)換。還可以想想用最精確的方式繪制一條水平或垂直的線條的方式不是描邊路徑,而是填充路徑。使用這種方法UIView的子類代碼將可以在任何設(shè)備上繪制一條完美的1px寬的垂線,代碼如下:
 圖12 整數(shù)的點(diǎn)坐標(biāo)與偏移0.5點(diǎn)的坐標(biāo)對(duì)應(yīng)的描邊處理 當(dāng)你遇到顯示效果不佳的時(shí),可能會(huì)被建議通過(guò)對(duì)坐標(biāo)增減0.5讓它在像素中居中。這個(gè)建議可能有效,如圖11。但它只是做了一些頭腦簡(jiǎn)單的假設(shè)。一個(gè)復(fù)雜的做法是獲得UIView的contentScaleFactor屬性。這個(gè)值為1.0或2.0,所以你可以除以這個(gè)屬性值得到從像素到點(diǎn)的轉(zhuǎn)換。還可以想想用最精確的方式繪制一條水平或垂直的線條的方式不是描邊路徑,而是填充路徑。使用這種方法UIView的子類代碼將可以在任何設(shè)備上繪制一條完美的1px寬的垂線,代碼如下:CGContextFillRect(con, CGRectMake(100,0,1.0/self.contentScaleFactor,100));  內(nèi)容模式一個(gè)視圖向它自身繪圖,相對(duì)于只有背景顏色和子視圖,它還有內(nèi)容。這意味著每當(dāng)視圖被調(diào)整大小它的contentMode屬性就變得非常重要。正如我之前提到的,繪圖系統(tǒng)會(huì)盡可能避免重頭開(kāi)始繪制視圖。相反,繪圖系統(tǒng)將使用之前繪圖操作的緩存結(jié)果(位圖回填)。所以,如果視圖被重新調(diào)整大小,系統(tǒng)可能簡(jiǎn)單的伸縮或重定位緩存繪圖,前提是你的contentMode設(shè)置指令是是這樣設(shè)置的。 說(shuō)明這一點(diǎn)略有點(diǎn)復(fù)雜。因?yàn)槲倚枰才耪{(diào)整視圖大小而不引起重繪操作(調(diào)用drawRect:方法)。當(dāng)程序啟動(dòng)時(shí),我將創(chuàng)建一個(gè)MyView實(shí)例,并將它放在window上。接著將執(zhí)行調(diào)整MyView尺寸的操作延遲到window出現(xiàn)和界面初次顯示之后:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];    self.window.rootViewController = [UIViewController new];    MyView* mv =[[MyView alloc] initWithFrame:CGRectMake(0, 0, self.window.bounds.size.width - 50, 150)];    mv.center = self.window.center;    [self.window.rootViewController.view addSubview: mv];    mv.opaque = NO;    mv.tag = 111; // so I can get a reference to this view later    [self performSelector:@selector(resize:) withObject:nil afterDelay:0.1];    self.window.backgroundColor = [UIColor whiteColor];    [self.window makeKeyAndVisible];    return YES;  }  我們將視圖的高度調(diào)成之前的2倍。沒(méi)有觸發(fā)drawRect:方法的調(diào)用。如果我們視圖的drawRect:方法代碼和生成圖9的代碼相同,則我們得到如圖12的結(jié)果,視圖被顯示在正確高度上。 圖13 內(nèi)容自動(dòng)伸展 可是早晚drawRect:方法會(huì)被調(diào)用,繪圖將按照drawRect:方法中的代碼被刷新。代碼不會(huì)將箭頭繪制在相對(duì)于視圖邊界的高度。它是在一個(gè)固定的高度。因此箭頭會(huì)伸展,而且會(huì)在以后某個(gè)時(shí)間返回到原始的尺寸。 通常我們的視圖的contentMode屬性需要與視圖繪制自己的方式一致。假設(shè)我們的drawRect:方法中的代碼讓箭頭的尺寸和位置相對(duì)于視圖的邊界原點(diǎn),即它的左上方。所以我們可以設(shè)置它的contentMode為UIViewContentModeTopLeft。又或者,我們可以將contentMode設(shè)置為UIVIewContentModeRedraw,這將引起緩存內(nèi)容的自動(dòng)縮放和重定位被關(guān)閉,最終結(jié)果是視圖的setNeedsDisplay方法將被調(diào)用,觸發(fā)drawRect:方法重繪視圖內(nèi)容。 在另一方面,如果一個(gè)視圖只是暫時(shí)被調(diào)整大小。假設(shè)是作為動(dòng)畫(huà)的一部分,那么伸縮行為正是你所想要的。假設(shè)我們的動(dòng)畫(huà)是想要讓視圖變大然后還原回原始大小以達(dá)到作為吸引用戶的一種手段。這就需要視圖伸縮的時(shí)候視圖的內(nèi)容也跟著伸縮,正確的contentMode的值是UIViewContentModeScaleToFill,被伸縮的內(nèi)容僅僅是視圖內(nèi)容的一副緩存圖片,所以它運(yùn)行起來(lái)十分的高效。
圖13 內(nèi)容自動(dòng)伸展 可是早晚drawRect:方法會(huì)被調(diào)用,繪圖將按照drawRect:方法中的代碼被刷新。代碼不會(huì)將箭頭繪制在相對(duì)于視圖邊界的高度。它是在一個(gè)固定的高度。因此箭頭會(huì)伸展,而且會(huì)在以后某個(gè)時(shí)間返回到原始的尺寸。 通常我們的視圖的contentMode屬性需要與視圖繪制自己的方式一致。假設(shè)我們的drawRect:方法中的代碼讓箭頭的尺寸和位置相對(duì)于視圖的邊界原點(diǎn),即它的左上方。所以我們可以設(shè)置它的contentMode為UIViewContentModeTopLeft。又或者,我們可以將contentMode設(shè)置為UIVIewContentModeRedraw,這將引起緩存內(nèi)容的自動(dòng)縮放和重定位被關(guān)閉,最終結(jié)果是視圖的setNeedsDisplay方法將被調(diào)用,觸發(fā)drawRect:方法重繪視圖內(nèi)容。 在另一方面,如果一個(gè)視圖只是暫時(shí)被調(diào)整大小。假設(shè)是作為動(dòng)畫(huà)的一部分,那么伸縮行為正是你所想要的。假設(shè)我們的動(dòng)畫(huà)是想要讓視圖變大然后還原回原始大小以達(dá)到作為吸引用戶的一種手段。這就需要視圖伸縮的時(shí)候視圖的內(nèi)容也跟著伸縮,正確的contentMode的值是UIViewContentModeScaleToFill,被伸縮的內(nèi)容僅僅是視圖內(nèi)容的一副緩存圖片,所以它運(yùn)行起來(lái)十分的高效。
| 
 
 | 
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注