UIBezierPath繪圖

UIBezierPath繼承自NSObject晕拆,位于UIKit框架中材蹬,是Core Graphics框架中與路徑相關(guān)的功能的封裝实幕。在iOS3.2及更高版本中整吆,可以使用UIBezierPath創(chuàng)建基于向量的路徑勇哗、繪制路徑的輪廓咽安、填充路徑包圍的空間等妆棒。其中路徑可以定義簡(jiǎn)單的形狀澡腾,如矩形,橢圓或弧形糕珊,也可以定義復(fù)合多邊形动分,包含直線和曲線段的混合。

1 Bezier path 與 UIBezierPath

Bezier path源于法國(guó)數(shù)學(xué)家皮埃爾·貝齊耶(PierreBézier)研制的“貝塞爾曲線”(Bezier curves)的概念红选。簡(jiǎn)單來(lái)說(shuō)澜公,它是幾個(gè)點(diǎn)之間的曲線,如下圖纠脾。

bezierPath.gif

關(guān)于貝塞爾曲線的深入學(xué)習(xí)可以參考Bezier path 資料玛瘸。

UIBezierPath對(duì)象是CGPathRef數(shù)據(jù)類型的封裝,用于創(chuàng)建基于向量的路徑苟蹈,而路徑是由線和曲線構(gòu)建的糊渊。每組連接的線和曲線段形成所謂的子路徑,共享相同的繪圖屬性慧脱,要繪制具有不同屬性的子路徑渺绒,必須將每個(gè)子路徑放在其自己的UIBezierPath對(duì)象中。

2 創(chuàng)建UIBezierPath對(duì)象

<a id = "2.1">

2.1 使用bezierPath方法創(chuàng)建自定義路徑對(duì)象

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

大多數(shù)情況下,對(duì)于多邊形或自定義圖形宗兼,我們一般從“空路徑”開(kāi)始躏鱼,通過(guò)移動(dòng)位置到初始點(diǎn),然后向下一個(gè)點(diǎn)添加線和弧線的方法創(chuàng)建路徑內(nèi)容殷绍。

</a>

2.2 使用bezierPathWithRect:方法創(chuàng)建矩形路徑

UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 200, 100)];
rectPath.png

2.3 使用bezierPathWithRoundedRect:cornerRadius:方法創(chuàng)建圓角矩形路徑

UIBezierPath *roundedRectPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 200, 100) cornerRadius:10];
roundedRectPath.png

2.4 使用 bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:方法創(chuàng)建自定義的圓角矩形路徑

UIBezierPath *roundedCornerPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 200, 100) byRoundingCorners:UIRectCornerTopLeft | UIRectCornerBottomRight cornerRadii:CGSizeMake(20, 20)];
roundedCornerPath.png

該方法可以指定將哪個(gè)角設(shè)置為圓角:
UIRectCornerTopLeft(左上角)
UIRectCornerTopRight(右上角)
UIRectCornerBottomLeft(左下角)
UIRectCornerBottomRight(右下角)
UIRectCornerAllCorners(所有角)

2.5 使用bezierPathWithOvalInRect:方法創(chuàng)建橢圓或圓形路徑

UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 200, 100)];
ovalPath.png

<a id="2.6">

2.6 使用 bezierPathWithArcCenter:radius:startAngle:endAngle:clockwise:方法創(chuàng)建弧形路徑

UIBezierPath *arcPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(200, 200) radius:100 startAngle:0 endAngle:M_PI clockwise:NO];
arcPath.png

該方法中的參數(shù):cenrer用于指定弧形的圓心染苛;radius指定弧形的半徑;startAngle指定弧形的起始角度主到;endAngle指定弧形的終止角度茶行;clockwise指定了弧形的繪制方向,是否為順時(shí)針登钥。上面的代碼中closewise設(shè)置為NO畔师,表示逆時(shí)針繪制。另外牧牢,值得注意的是看锉,這里指定的起始和終止角度都需要以弧度表示,在默認(rèn)的坐標(biāo)系中繪制時(shí)塔鳍,起始和終止角度的定義基于下圖所示的單位圓伯铣。

startAngle&endAngle.jpg

</a>

2.7 使用bezierPathByReversingPath方法創(chuàng)建反向路徑

UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 200, 100)];
UIBezierPath *reversedPath = [rectPath bezierPathByReversingPath];
reversedPath.jpg

需要注意的是,這是一個(gè)實(shí)例方法献幔,而上面其他幾個(gè)都是類方法懂傀。使用該方法繪制時(shí),它不一定會(huì)改變路徑的形狀蜡感,相反蹬蚁,它改變的是路徑的繪制方向(交換起點(diǎn)和終點(diǎn)),從而可能影響到路徑的填充效果郑兴。上面的代碼中犀斋,rectPath默認(rèn)是順時(shí)針繪制的(如左圖所示),對(duì)它調(diào)用該方法后情连,雖然路徑形狀沒(méi)有變化叽粹,但是是逆時(shí)針?lè)较蚶L制的(如右圖所示)。

3 構(gòu)建自定義路徑

構(gòu)建自定義路徑却舀,首先需要使用2.1介紹的方法創(chuàng)建一個(gè)新的空路徑對(duì)象虫几,通過(guò)選擇一個(gè)初始點(diǎn)并調(diào)用moveToPoint:方法移動(dòng)到該點(diǎn),然后就可以開(kāi)始向該點(diǎn)添加線或曲線挽拔。添加新的線或曲線總是假定從當(dāng)前位置開(kāi)始辆脸,并以指定的某個(gè)點(diǎn)結(jié)束。每次添加后螃诅,新一段的終點(diǎn)自動(dòng)變成下一段的起點(diǎn)啡氢,也是當(dāng)前點(diǎn)(currentPoint)状囱。結(jié)束時(shí),通過(guò)調(diào)用closePath方法從當(dāng)前點(diǎn)向子路徑中的第一個(gè)點(diǎn)添加一條直線段來(lái)關(guān)閉路徑倘是。

3.1 使用addLineToPoint:方法構(gòu)建直線路徑

// 1 創(chuàng)建路徑對(duì)象
UIBezierPath *bezierPath = [UIBezierPath bezierPath];

// 2 移動(dòng)到起點(diǎn)
[bezierPath moveToPoint:CGPointMake(100, 100)];

// 3 添加直線
[bezierPath addLineToPoint:CGPointMake(300, 300)];
addLine.png

3.2 使用addArcWithCenter:radius:startAngle:endAngle:clockwise:方法構(gòu)建弧形路徑

// 1 創(chuàng)建路徑對(duì)象
UIBezierPath *bezierPath = [UIBezierPath bezierPath];

// 2 移動(dòng)到起點(diǎn)
[bezierPath moveToPoint:CGPointMake(100, 100)];

// 3 添加弧線
[bezierPath addArcWithCenter:CGPointMake(200, 200) radius:100 startAngle:0 endAngle:M_PI clockwise:YES];
addArc.png

這里定義弧線的方法與創(chuàng)建弧形路徑對(duì)象的方法類似亭枷,詳細(xì)參數(shù)可以參考2.6中的說(shuō)明。

3.3 使用addQuadCurveToPoint:controlPoint:方法和addCurveToPoint:controlPoint1:controlPoint2:方法構(gòu)建曲線路徑

我們常見(jiàn)的曲線形狀是使用起點(diǎn)和終點(diǎn)之間的切線以及一個(gè)或多個(gè)控制點(diǎn)進(jìn)行定義的搀崭。UIBezierPath為我們提供了二次貝塞爾曲線(只有一個(gè)控制點(diǎn))和三次貝塞爾曲線(有兩個(gè)控制點(diǎn))的繪制方法叨粘。下圖展示了這兩種曲線的區(qū)別。

BezierCurve&QuadCurve.png
3.3.1 二次曲線 (quadratic bezier curve)
// 1 創(chuàng)建路徑對(duì)象
UIBezierPath *bezierPath = [UIBezierPath bezierPath];

// 2 移動(dòng)到起點(diǎn)
[bezierPath moveToPoint:CGPointMake(100, 200)];

// 3 添加二次曲線
[bezierPath addQuadCurveToPoint:CGPointMake(300, 200) controlPoint:CGPointMake(250, 100)];
quadraticCurve.png
3.3.2 三次曲線 (cubic bezier curve)
// 1 創(chuàng)建路徑對(duì)象
UIBezierPath *bezierPath = [UIBezierPath bezierPath];

// 2 移動(dòng)到起點(diǎn)
[bezierPath moveToPoint:CGPointMake(100, 200)];

// 3 添加三次曲線
[bezierPath addCurveToPoint:CGPointMake(300, 200) controlPoint1:CGPointMake(200, 100) controlPoint2:CGPointMake(200, 300)];
cubicCurve.png

3.4 使用closePath方法關(guān)閉路徑

    // 1 創(chuàng)建路徑對(duì)象
    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    
    // 2 移動(dòng)到起點(diǎn)
    [bezierPath moveToPoint:CGPointMake(200, 200)];
    
    // 3 添加直線
    [bezierPath addLineToPoint:CGPointMake(300, 400)];
    [bezierPath addLineToPoint:CGPointMake(100, 400)];
    
    // 4 關(guān)閉路徑
    [bezierPath closePath];
closePath.png

使用該方法閉合路徑時(shí)瘤睹,會(huì)添加一條從當(dāng)前點(diǎn)到起點(diǎn)的直線宣鄙,因此在上面的代碼中,相當(dāng)于我們使用[bezierPath closePath]替代了[bezierPath addLineToPoint:CGPointMake(200, 200)]默蚌。

3.5 使用appendPath:方法追加路徑

// 1 創(chuàng)建路徑
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath addArcWithCenter:CGPointMake(200, 200) radius:110 startAngle:0 endAngle:2 * M_PI clockwise:YES];

// 2 追加一個(gè)圓形路徑
UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(125, 125, 150, 150)];
[bezierPath appendPath:circle];

// 3 追加一個(gè)正方形路徑
UIBezierPath *square = [UIBezierPath bezierPathWithRect:CGRectMake(180, 180, 40, 40)];
[bezierPath appendPath:square];
appendPath.png

3.6 使用removeAllPoints方法刪除路徑

// 1 創(chuàng)建路徑
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath addArcWithCenter:CGPointMake(200, 200) radius:110 startAngle:0 endAngle:2 * M_PI clockwise:YES];

// 2 追加一個(gè)圓形路徑
UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(125, 125, 150, 150)];
[bezierPath appendPath:circle];

// 3 創(chuàng)建一個(gè)正方形路徑
UIBezierPath *square = [UIBezierPath bezierPathWithRect:CGRectMake(180, 180, 40, 40)];

// 4 刪除該正方形路徑
[square removeAllPoints];

// 5 追加該正方形路徑
[bezierPath appendPath:square];
removePath.png

4 渲染路徑

4.1 設(shè)置渲染路徑的屬性

4.1.1 lineWidth 渲染路徑的線條寬度
lineWidth.png

默認(rèn)是1.0。

4.1.2 lineCapStyle 渲染路徑的線條端點(diǎn)的形狀:kCGLineCapButt(默認(rèn))苇羡、kCGLineCapRound绸吸、kCGLineCapSquare
lineCap.png
4.1.3 lineJoinStyle 渲染路徑的線條接合點(diǎn)的形狀:kCGLineJoinMiter(默認(rèn))设江、kCGLineJoinRound锦茁、kCGLineJoinBevel
lineJoin.png
4.1.4 miterLimit 渲染路徑時(shí)用于避免線段連接處形成尖峰的斜角極限
miterLimit2.jpg

默認(rèn)是10叉存。

4.1.5 flatness 渲染曲線路徑的平坦度
flatness.png

默認(rèn)平坦度值為0.6码俩。在大多數(shù)情況下,我們不需要更改平坦度歼捏。

4.1.6 usesEvenOddFillRule渲染路徑時(shí)是否使用奇偶規(guī)則
fillRules.png

如果為YES稿存,則使用奇偶規(guī)則填充路徑。如果為NO瞳秽,則使用非零規(guī)則填充瓣履。默認(rèn)是NO。

填充路徑時(shí)练俐,一般通過(guò)兩種方式計(jì)算填充區(qū)域:非零繞組數(shù)規(guī)則(nonzero winding number rule)和奇偶規(guī)則(even-odd rule)袖迎。
要確定哪些區(qū)域被填充,一般從給定區(qū)域內(nèi)的點(diǎn)開(kāi)始腺晾,向路徑邊界以外的任何點(diǎn)繪制一條射線燕锥,交叉路徑線的總數(shù)決定了填充區(qū)域。

對(duì)于非零規(guī)則悯蝉,繪制路徑段的方向影響結(jié)果归形。從左到右路徑的交叉點(diǎn)計(jì)數(shù)為+1,從右到左路徑的交叉點(diǎn)計(jì)數(shù)為-1泉粉。如果交叉點(diǎn)的總和不為0连霉,則該點(diǎn)被認(rèn)為在路徑內(nèi)榴芳,并且對(duì)應(yīng)的區(qū)域被填充。如果交叉點(diǎn)的總和為0跺撼,則該點(diǎn)在路徑外部窟感,并且該區(qū)域不被填充。上圖左邊顯示了使用非零繞組數(shù)規(guī)則填充的兩組內(nèi)圈和外圈歉井。當(dāng)每個(gè)圓圈以相同方向繪制時(shí)柿祈,兩個(gè)圓圈都被填充。當(dāng)圓圈以相反方向繪制時(shí)哩至,內(nèi)圈未被填充躏嚎。

對(duì)于奇偶規(guī)則,計(jì)算路徑交叉的總數(shù)菩貌,如果結(jié)果為奇數(shù)卢佣,則該點(diǎn)被認(rèn)為在路徑內(nèi),并且對(duì)應(yīng)的區(qū)域被填充箭阶。如果結(jié)果為偶數(shù)虚茶,則該點(diǎn)被認(rèn)為是在路徑之外,并且該區(qū)域不被填充仇参。繪制路徑段的方向不影響結(jié)果嘹叫。如上圖右邊所示,繪制每個(gè)圓的方向并不重要诈乒,填充將始終如圖所示罩扇。

<a id="4.1.7">

4.1.7 setLineDash:count:phase: 設(shè)置渲染路徑的線條形狀
setLineDash.gif
// 1 創(chuàng)建路徑對(duì)象
UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 100)];

// 2 定義一個(gè)浮點(diǎn)值的C風(fēng)格數(shù)組以指定線段的長(zhǎng)度及間隔
CGFloat pattern[4] = {8.0, 3.0, 20.0, 5.0};

// 3 設(shè)置線條形狀
[rectPath setLineDash:pattern count:4 phase:0];

// 4 設(shè)置線條寬度
[rectPath setLineWidth:4.0];

// 5 繪制路徑
[rectPath stroke];
linePattern.png

該方法中怜奖,第一個(gè)參數(shù)pattern是浮點(diǎn)值的C風(fēng)格數(shù)組之景,指定了線段的長(zhǎng)度及間隔这橙,數(shù)組中的值交替定義媳拴,第一個(gè)是線段的長(zhǎng)度蜂科,后跟第一個(gè)線段間隔長(zhǎng)度涂召,再是第二個(gè)線段長(zhǎng)度瓶埋,再跟第二個(gè)線段間隔長(zhǎng)度篮愉,以此類推滩届。
第二個(gè)參數(shù)count指定了pattern數(shù)組中的元素個(gè)數(shù)集侯。
第三個(gè)參數(shù)phase指定畫線模式的起始點(diǎn),即開(kāi)始繪制時(shí)的偏移位置帜消,以畫線模式的點(diǎn)計(jì)算棠枉,正值向右偏移,負(fù)值向左偏移泡挺。

</a>

4.1.8 getLineDash:count:phase: 檢索渲染路徑的線條畫線模式

參數(shù)同上

4.2 渲染路徑

4.2.1 使用fill方法填充由路徑包圍的區(qū)域
// 1 創(chuàng)建一個(gè)矩形路徑對(duì)象
UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 100)];

// 2 設(shè)置路徑填充顏色
[[UIColor redColor] setFill];

// 3 填充路徑
[rectPath fill];   
fillPath.png

<a id = "4.2.2">

4.2.2 使用fillWithBlendMode:alpha:方法指定混合模式和透明度值來(lái)填充路徑
// 1 創(chuàng)建一個(gè)背景矩形對(duì)象
UIBezierPath *backRect = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 200, 200)];

// 2 設(shè)置背景矩形的填充顏色
[[UIColor greenColor] setFill];

// 3 填充背景矩形
[backRect fill];
    
// 4 創(chuàng)建一個(gè)前景矩形對(duì)象
UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 100)];

// 5 設(shè)置前景矩形的填充顏色
[[UIColor redColor] setFill];

// 6 使用指定的混合模式和透明度值填充前景路徑
[rectPath fillWithBlendMode:kCGBlendModeNormal alpha:0.5];
fillBlend.png

該方法中有兩個(gè)參數(shù)辈讶。
第一個(gè)參數(shù)blendMode用于確定填充路徑如何與現(xiàn)有的渲染內(nèi)容合成,在上面的代碼中我們的使用正陈γǎ混合模式kCGBlendModeNormal贱除,除此之外還有多重混合模式kCGBlendModeMultiply和疊加混合模式kCGBlendModeOverlay等生闲,詳細(xì)內(nèi)容可以參考文檔中關(guān)于CGBlendMode的介紹。
第二個(gè)參數(shù)alpha用于確定填充路徑的透明度月幌,范圍在0.0(透明)和1.0(不透明)之間碍讯。

</a>

4.2.3 使用stroke方法跟蹤并繪制路徑的輪廓
// 1 創(chuàng)建一個(gè)矩形路徑對(duì)象
UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 100)];
    
// 2 設(shè)置繪制路徑的畫筆顏色
[[UIColor redColor] setStroke];
    
// 3 設(shè)置繪制路徑的線條寬度
[rectPath setLineWidth:3.0];
    
// 4 繪制路徑
[rectPath stroke];
strokePath.png
4.2.4 使用strokeWithBlendMode:alpha:方法指定混合模式和透明度值來(lái)繪制路徑
// 1 創(chuàng)建背景矩形
UIBezierPath *backRect = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 200, 200)];
[[UIColor greenColor] setFill];
[backRect fill];
    
// 2 創(chuàng)建前景矩形
UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 100)];
// 2.1 設(shè)置繪制前景矩形的畫筆顏色
[[UIColor redColor] setStroke];
// 2.2 設(shè)置繪制路徑的線條寬度
[rectPath setLineWidth:10.0];
// 2.3 使用指定的混合模式和透明度值填充前景路徑
[rectPath strokeWithBlendMode:kCGBlendModeNormal alpha:0.5];
strokeBlend.png

該方法中的參數(shù)說(shuō)明可以參考fillWithBlendMode:alpha:方法中的注釋。

5 剪切路徑

使用addClip方法剪切路徑

// 1 創(chuàng)建兩個(gè)圓形路徑
UIBezierPath *path1 = [UIBezierPath bezierPathWithArcCenter:CGPointMake(150, 200) radius:100 startAngle:0 endAngle:2 * M_PI clockwise:YES];
UIBezierPath *path2 = [UIBezierPath bezierPathWithArcCenter:CGPointMake(250, 200) radius:100 startAngle:0 endAngle:2 * M_PI clockwise:YES];

// 2 繪制路徑
[path1 stroke];
[path2 stroke];

// 3 剪切路徑
[path1 addClip];

// 4 填充路徑
[[UIColor redColor] setFill];
[path2 fill];
clipPath.png

該方法會(huì)修改繪圖的可見(jiàn)區(qū)域扯躺,調(diào)用之后捉兴,隨后的繪圖操作只對(duì)指定路徑內(nèi)的區(qū)域有效。在上面的代碼中录语,path1剪切路徑后倍啥,path2只保留與path1相交的路徑區(qū)域,因此在對(duì)path2進(jìn)行填充操作時(shí)澎埠,只有相交區(qū)域被填充虽缕。

6 轉(zhuǎn)換路徑

使用applyTransform:方法轉(zhuǎn)換路徑

// 1 創(chuàng)建路徑對(duì)象
UIBezierPath *originPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 100)];

// 2 轉(zhuǎn)換路徑
[originPath applyTransform:CGAffineTransformMakeTranslation(100, 100)];

// 3 填充路徑
[[UIColor redColor] setFill];
[originPath fill];
transformPath.png

該方法會(huì)使用指定的仿射變換矩陣來(lái)轉(zhuǎn)換路徑中的所有點(diǎn),這里的參數(shù)transform就是用于確定將哪種轉(zhuǎn)換矩陣應(yīng)用于路徑中彼宠,在上面的代碼中使用了CGAffineTransformMakeTranslation,除此之外還有CGAffineTransformMakeScaleCGAffineTransformMakeRotation等,詳細(xì)內(nèi)容可以參考文檔中關(guān)于CGAffineTransform的介紹弟塞。

7 路徑的點(diǎn)擊測(cè)試

7.1 使用currentPoint屬性表示路徑的當(dāng)前點(diǎn)

// 1 創(chuàng)建路徑
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(100, 100)];
[path addArcWithCenter:CGPointMake(200, 200) radius:50 startAngle:1.5 * M_PI endAngle:M_PI clockwise:NO];

// 2 繪制路徑
[path setLineWidth:2.0];
[path stroke];

// 3 打印路徑的當(dāng)前點(diǎn)
NSLog(@"The current point of the path is (%.2f, %.2f)", path.currentPoint.x, path.currentPoint.y);
currentPoint.png

該屬性中的值也表示新路徑段的起點(diǎn)。如果路徑當(dāng)前為空拙已,則該屬性包含CGPointZero决记。

7.2 使用bounds屬性表示完全包圍路徑中所有點(diǎn)的最小矩形

// 1 創(chuàng)建一條直線路徑
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(100, 100)];
[path addLineToPoint:CGPointMake(300, 300)];

// 2 繪制直線路徑
[path setLineWidth:3.0];
[[UIColor redColor] setStroke];
[path stroke];

// 3 以直線路徑的邊界創(chuàng)建一個(gè)矩形路徑
UIBezierPath *boundsPath = [UIBezierPath bezierPathWithRect:path.bounds];

// 4 繪制矩形路徑
[boundsPath setLineWidth:5.0];
[[UIColor blueColor] setStroke];
[boundsPath stroke];
bounds.png

7.3 使用empty屬性判斷路徑中是否具有任何有效路徑元素

// 1 創(chuàng)建路徑對(duì)象
UIBezierPath *path = [UIBezierPath bezierPath];

// 2 移動(dòng)路徑到指定點(diǎn)
[path moveToPoint:CGPointMake(100, 100)];

// 3 點(diǎn)擊測(cè)試
if (path.empty == YES) {
    NSLog(@"The path has no valid elements");
}
else if (path.empty == NO) {
    NSLog(@"The path has some valid elements");
}
emptyTest.png

這里所說(shuō)的有效路徑元素包括移動(dòng)到指定點(diǎn)的命令、繪制線或曲線的命令以及關(guān)閉路徑的命令倍踪。 因此系宫,即使上面的代碼只調(diào)用了moveToPoint:方法,路徑也不被視為空建车。

7.4 使用containsPoint:方法判斷路徑包圍的區(qū)域是否包含指定的點(diǎn)扩借。

// 1 創(chuàng)建路徑
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(200, 200) radius:100 startAngle:0 endAngle:2 * M_PI clockwise:YES];
[path appendPath:[UIBezierPath bezierPathWithArcCenter:CGPointMake(200, 200) radius:75 startAngle:0 endAngle:2 * M_PI clockwise:NO]];
[path appendPath:[UIBezierPath bezierPathWithArcCenter:CGPointMake(200, 200) radius:50 startAngle:0 endAngle:2 * M_PI clockwise:YES]];

// 2 設(shè)置渲染路徑的屬性
[[UIColor redColor] set];
[path setLineWidth:3.0];

// 3 點(diǎn)擊測(cè)試
if ([path containsPoint:CGPointMake(260, 210)] == YES) {
    [path fill];
}
else if ([path containsPoint:CGPointMake(260, 210)] == NO) {
    [path stroke];
}
containsPoint.png

8 UIBezierPath的應(yīng)用

UIBezierPath常見(jiàn)的用途之一就是將圖像裁剪成自定義形狀,如用戶頭像等缤至。
這里我們所介紹的demo也將展示圖片裁剪的過(guò)程潮罪,除此之外,還展示了刮開(kāi)涂層(類似于刮刮樂(lè))的效果领斥。大致思路是嫉到,在視圖中的同一位置添加兩個(gè)大小相同的imageView使之重疊,然后分別將圖片裁剪為相同的圓形月洛,同樣是重疊的效果何恶,即上面一張圓形圖片(前景)完全覆蓋下面一張圓形圖片(背景),當(dāng)我們刮開(kāi)上面的圖片嚼黔,下面的圖片逐漸顯示细层,最終效果圖如下:

demo.gif

完整代碼可以參考UIBezierPathDemo。下面是詳細(xì)介紹疫赎。

8.1 創(chuàng)建項(xiàng)目

打開(kāi)Xcode撵彻,創(chuàng)建一個(gè)新的項(xiàng)目(File\New\Project...)陌僵,選擇iOS一欄下Application中的Single View Application模版,點(diǎn)擊Next偎谁。

createAProject.png

在Product Name中填寫UIBezierPathDemo,點(diǎn)擊Next铐望。選擇文件位置正蛙,點(diǎn)擊Create創(chuàng)建工程。

productName.png

8.2 構(gòu)建界面

在這里我們使用純代碼構(gòu)建锻全。打開(kāi)ViewController.m文件,定義兩個(gè)UIImageView對(duì)象的屬性部翘。

@interface ViewController ()

@property (nonatomic, strong) UIImageView *bgImageView;      // 背景
@property (nonatomic, strong) UIImageView *fgImageView;      // 前景

@end

打開(kāi)Assets.xcassets文件新思,添加兩張圖片(圖片資源)纵刘。

addImage.png

打開(kāi)ViewController.m文件,在viewDidLoad方法中添加下面的代碼舵抹。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 創(chuàng)建背景imageView并添加到視圖中
    self.bgImageView = [[UIImageView alloc] initWithFrame:CGRectMake(50, 150, 300, 300)];
    [self.view addSubview:self.bgImageView];
    
    // 為背景imageView設(shè)置圖片
    UIImage *bgImage = [UIImage imageNamed:@"bgImage"];
    self.bgImageView.image = bgImage;
    
    // 創(chuàng)建前景imageView并添加到視圖中
    self.fgImageView = [[UIImageView alloc] initWithFrame:self.bgImageView.frame];
    [self.view addSubview:self.fgImageView];
    
    // 為前景imageView設(shè)置圖片
    UIImage *fgImage = [UIImage imageNamed:@"fgImage"];
    self.fgImageView.image = fgImage;
}

運(yùn)行一下,可以看到只顯示了前景圖片(背景圖片被覆蓋)香嗓。

displayImage.png

8.3 裁剪圖片

viewDidLoad方法的下面,我們定義一個(gè)clipImage:方法用于執(zhí)行裁剪圖片的操作饱岸,代碼如下:

- (UIImage *)clipImage:(UIImage *)image
{
    // 開(kāi)啟一個(gè)與圖像大小一致的圖形上下文
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
    
    // 創(chuàng)建一個(gè)圓形的路徑
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
    
    // 剪切圓形路徑
    [path addClip];
   
    // 將圖片繪制到圖形上下文中
    [image drawAtPoint:CGPointZero];
    
    // 從當(dāng)前圖形上下文中獲取被裁剪的圖片
    UIImage *clippedImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 關(guān)閉圖形上下文
    UIGraphicsEndImageContext();
    
    // 返回被裁剪的圖片
    return clippedImage;
}

在這里我們沒(méi)有將繪制方法寫在drawRect:方法中汤锨,而是單獨(dú)寫在了自定義的裁剪方法里牍汹,所以我們沒(méi)有任何上下文可用慎菲,因此要生成一個(gè)新的圖像就需要開(kāi)啟一個(gè)新的圖形上下文,相關(guān)內(nèi)容可以參考Drawing and Creating Images文檔中的描述解幼。
裁剪圖片主要是通過(guò)對(duì)UIBezierPath對(duì)象調(diào)用addClip方法來(lái)實(shí)現(xiàn)的底靠,上面的代碼中最后返回一個(gè)從圖形上下文中獲取到UIImage對(duì)象(即被裁剪后的圖像)暑中。

之后,在viewDidLoad方法中通過(guò)調(diào)用clipImage:方法严衬,將imageView的圖片重新設(shè)置為被裁剪的圖片请琳,修改后的代碼如下:

- (void)viewDidLoad
{   
    ...
    // 為背景imageView設(shè)置圖片
    UIImage *bgImage = [UIImage imageNamed:@"bgImage"];
    self.bgImageView.image = [self clipImage:bgImage];
    
    ...
    // 為前景imageView設(shè)置圖片
    UIImage *fgImage = [UIImage imageNamed:@"fgImage"];
    self.fgImageView.image = [self clipImage:fgImage];
}

運(yùn)行一下榕堰,可以看到圖片被裁剪為圓形

clipImage.png

8.4 實(shí)現(xiàn)刮開(kāi)涂層的效果

刮開(kāi)涂層的效果主要是通過(guò)重寫touchesMoved:withEvent:方法實(shí)現(xiàn)的圾旨,詳細(xì)代碼如下:

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    
    // 創(chuàng)建一個(gè)UITouch對(duì)象
    UITouch *touch = touches.anyObject;
    
    // 設(shè)置觸摸位置在前景圖片上的坐標(biāo)
    CGPoint touchPoint = [touch locationInView:self.fgImageView];
    
    // 設(shè)置觸摸時(shí)清除點(diǎn)的大小
    CGRect touchRect = CGRectMake(touchPoint.x, touchPoint.y, 20, 20);
    
    // 開(kāi)啟一個(gè)與前景圖像大小一致的圖形上下文
    UIGraphicsBeginImageContextWithOptions(self.fgImageView.bounds.size, NO, 0);
    
    // 獲取當(dāng)前上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 將前景圖像視圖的圖層渲染到當(dāng)前上下文中
    [self.fgImageView.layer renderInContext:context];
    
    // 清除觸摸過(guò)的區(qū)域
    CGContextClearRect(context, touchRect);
    
    // 從當(dāng)前圖形上下文中獲取圖片(即刮開(kāi)的圖片)
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    
    // 關(guān)閉圖形上下文
    UIGraphicsEndImageContext();
    
    // 設(shè)置前景imageView的圖片為刮開(kāi)的圖片
    self.fgImageView.image = image;
}

運(yùn)行程序,用鼠標(biāo)在圖片上劃動(dòng)廓鞠,可以看到如下效果:

touchesEvent.png

關(guān)于touchesMoved:withEvent:的詳細(xì)介紹可以參考文檔。

9 參考資料

1 UIBezierPath - UIKit | Apple Developer Documentation

2 Drawing Shapes Using Bézier Paths

3 Bezier Paths in practice

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市怨绣,隨后出現(xiàn)的幾起案子减细,更是在濱河造成了極大的恐慌,老刑警劉巖茧妒,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異狰腌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)丹莲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事『蟮拢” “怎么了理张?”我有些...
    開(kāi)封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)织狐。 經(jīng)常有香客問(wèn)我暂幼,道長(zhǎng),這世上最難降的妖魔是什么移迫? 我笑而不...
    開(kāi)封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任旺嬉,我火速辦了婚禮,結(jié)果婚禮上厨埋,老公的妹妹穿的比我還像新娘邪媳。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布顿肺。 她就那樣靜靜地躺著,像睡著了一般赃绊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卦方,一...
    開(kāi)封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天吗跋,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛慨默,可吹牛的內(nèi)容都是我干的蒜胖。 我是一名探鬼主播樊拓,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼赴叹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柱宦!你這毒婦竟也來(lái)了忧侧?” 一聲冷哼從身側(cè)響起犀暑,我...
    開(kāi)封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤几睛,失蹤者是張志新(化名)和其女友劉穎钻蹬,沒(méi)想到半個(gè)月后旗国,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筋遭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秽誊。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖琳骡,靈堂內(nèi)的尸體忽然破棺而出锅论,到底是詐尸還是另有隱情,我是刑警寧澤楣号,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布最易,位于F島的核電站,受9級(jí)特大地震影響炫狱,放射性物質(zhì)發(fā)生泄漏耘纱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一毕荐、第九天 我趴在偏房一處隱蔽的房頂上張望束析。 院中可真熱鬧,春花似錦憎亚、人聲如沸员寇。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蝶锋。三九已至陆爽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扳缕,已是汗流浹背慌闭。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留躯舔,地道東北人驴剔。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像粥庄,于是被迫代替她去往敵國(guó)和親丧失。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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