UIBezierPath類詳細解析(三) —— 基本使用(二)

版本記錄

版本號 時間
V1.0 2018.01.18

前言

在iOS中不管是畫圖或者動畫,都需要指名路徑,我們經常用的就是UIBezierPath貝塞爾路徑,接下來這幾篇我們就詳細的介紹下這個類和基本用法。感興趣可以看上面寫的幾篇。
1. UIBezierPath類詳細解析(一) —— 基本概覽
2. UIBezierPath類詳細解析(二) —— 基本使用(一)

路徑信息 - Path info

關于路徑信息主要是這幾個屬性和方法。

@property(readonly,getter=isEmpty) BOOL empty;
@property(nonatomic,readonly) CGRect bounds;
@property(nonatomic,readonly) CGPoint currentPoint;
- (BOOL)containsPoint:(CGPoint)point;

1. @property(readonly,getter=isEmpty) BOOL empty;

這是一個只讀屬性。

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(200, 10)];
    [path addLineToPoint:CGPointMake(200, 400)];

    BOOL status = path.isEmpty;
    NSLog(@"是否為空 = %d", status);

看一下輸出結果

2018-01-18 17:15:19.692788+0800 JJLayer_demo1[41376:27182748] 是否為空 = 0

這就說明路徑不為空,下面我們修改成下面的代碼

    UIBezierPath *path = [UIBezierPath bezierPath];
    BOOL status = path.isEmpty;
    NSLog(@"是否為空 = %d", status);

下面看輸出結果

2018-01-18 17:16:07.454364+0800 JJLayer_demo1[41396:27187586] 是否為空 = 1

這就說明路徑為空,也不能繪制。

2. @property(nonatomic,readonly) CGRect bounds;

這個也是一個只讀屬性。

下面看一下代碼

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(200, 10)];
    [path addLineToPoint:CGPointMake(200, 400)];
    CGRect rect = path.bounds;
    NSLog(@"rect = %lf,%lf,%lf,%lf", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);

下面看一下輸出結果

2018-01-18 17:20:57.721579+0800 JJLayer_demo1[41478:27211036] rect = 200.000000,10.000000,0.000000,390.000000

這里,可以注意到寬度一直為0.0,具體效果如下所示。

3. @property(nonatomic,readonly) CGPoint currentPoint;

該屬性還是只讀屬性,目的就是獲取繪制結束后的當前點的位置,下面看一下代碼。

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(200, 10)];
    [path addLineToPoint:CGPointMake(200, 400)];
    CGPoint point = path.currentPoint;
    NSLog(@"point = %lf,%lf", point.x, point.y);

下面我們就看一下輸出結果

2018-01-18 17:23:22.079304+0800 JJLayer_demo1[41511:27230619] point = 200.000000,400.000000

這個還是很好理解的。

4. - (BOOL)containsPoint:(CGPoint)point;

該方法的作用就是用于判斷路徑是否包含某一點。

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(200, 10)];
    [path addLineToPoint:CGPointMake(200, 400)];
    BOOL value1 = [path containsPoint:CGPointMake(200.0, 200.0)];
    NSLog(@"是否包含 = %d", value1);
    BOOL value2 = [path containsPoint:CGPointMake(300.0, 200.0)];
    NSLog(@"是否包含 = %d", value2);

下面看輸出結果

2018-01-18 17:26:14.161382+0800 JJLayer_demo1[41545:27246352] 是否包含 = 1
2018-01-18 17:26:14.161528+0800 JJLayer_demo1[41545:27246352] 是否包含 = 0

這個方法也是很好理解的。


繪制屬性 - Drawing properties

相關的屬性和方法如下所示。

@property(nonatomic) CGFloat lineWidth;
@property(nonatomic) CGLineCap lineCapStyle;
@property(nonatomic) CGLineJoin lineJoinStyle;
@property(nonatomic) CGFloat miterLimit; // Used when lineJoinStyle is kCGLineJoinMiter
@property(nonatomic) CGFloat flatness;
@property(nonatomic) BOOL usesEvenOddFillRule; // Default is NO. When YES, the even-odd fill rule is used for drawing, clipping, and hit testing.

- (void)setLineDash:(nullable const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase;
- (void)getLineDash:(nullable CGFloat *)pattern count:(nullable NSInteger *)count phase:(nullable CGFloat *)phase;

1. @property(nonatomic) CGFloat lineWidth;

線寬,這個就不多說了。

2. @property(nonatomic) CGLineCap lineCapStyle;

線頭的樣式,是一個枚舉,在CAShapeLayer類解析(二) —— 基本使用中講述過。不同的是那里是字符串,這里是枚舉。但是樣式都是一樣的,感興趣的可以參考那一篇文章。

/* Line cap styles. */

typedef CF_ENUM(int32_t, CGLineCap) {
    kCGLineCapButt,
    kCGLineCapRound,
    kCGLineCapSquare
};

3. @property(nonatomic) CGLineJoin lineJoinStyle;

線連接點的樣式,是一個枚舉,在CAShapeLayer類解析(二) —— 基本使用中講述過。不同的是那里是字符串,這里是枚舉。但是樣式都是一樣的,感興趣的可以參考那一篇文章。

/* Line join styles. */

typedef CF_ENUM(int32_t, CGLineJoin) {
    kCGLineJoinMiter,
    kCGLineJoinRound,
    kCGLineJoinBevel
};

4. @property(nonatomic) CGFloat miterLimit;

這個只有在kCGLineJoinMiter時才會生效。在CAShapeLayer類解析(二) —— 基本使用中講述過。

5. @property(nonatomic) CGFloat flatness;

彎曲路徑的渲染精度,默認為0.6,越小精度越高,相應的更加消耗性能。這個精度和性能不好測量,就不給大家展示效果圖了。

6. @property(nonatomic) BOOL usesEvenOddFillRule;

用于判斷一個閉合path的填充區(qū)域,是否使用奇偶填充原則。默認是NO, 當“YES”時,奇偶填充規(guī)則用于繪圖,裁剪和命中測試。與奇偶填充原則對應的還有一個就是non-zero原則。

non-zero字面意思是“非零”。按該規(guī)則,要判斷一個點是否在圖形內,從該點作任意方向的一條射線,然后檢測射線與圖形路徑的交點情況。從0開始計數,路徑從左向右穿過射線則計數加1,從右向左穿過射線則計數減1。得出計數結果后,如果結果是0,則認為點在圖形外部,否則認為在內部。

even-odd字面意思是“奇偶”。按該規(guī)則,要判斷一個點是否在圖形內,從該點作任意方向的一條射線,然后檢測射線與圖形路徑的交點的數量。如果結果是奇數則認為點在內部,是偶數則認為點在外部。

Default is NO. When YES, the even-odd fill rule is used for drawing, clipping, and hit testing.
If YES, the path is filled using the even-odd rule. If NO, it is filled using the non-zero rule. Both rules are algorithms to determine which areas of a path to fill with the current fill color. A ray is drawn from a point inside a given region to a point anywhere outside the path’s bounds. The total number of crossed path lines (including implicit path lines) and the direction of each path line are then interpreted as follows:
For the even-odd rule, if the total number of path crossings is odd, the point is considered to be inside the path and the corresponding region is filled. If the number of crossings is even, the point is considered to be outside the path and the region is not filled.
For the non-zero rule, the crossing of a left-to-right path counts as +1 and the crossing of a right-to-left path counts as -1. If the sum of the crossings is nonzero, the point is considered to be inside the path and the corresponding region is filled. If the sum is 0, the point is outside the path and the region is not filled.

7. - (void)setLineDash:(nullable const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase;

下面我們看一下這幾個參數:

  • pattern:這是C類型的浮點型數組,里面包含的數字依次是線寬,空白的間隔,線寬,空白的間隔...
  • count:pattern中數組元素個數。
  • phase:開始繪制時給出的偏移量,例如,如果設置為6,pattern數組為5-2-3-2,那么就會跳過第一個5的線段,從空格2中間的部分開始繪制。

用于繪制虛線的一個方法,下面直接看一下代碼。

#import "JJCustomView.h"

@implementation JJCustomView

- (void)drawRect:(CGRect)rect
{
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path addArcWithCenter:CGPointMake(200.0, 200.0) radius:100.0 startAngle:0.0 endAngle:M_PI * 2 clockwise:YES];
    CGFloat dashStyle[] = {5.0, 2.0, 3.0, 2.0};
    [path setLineDash:dashStyle count:4 phase:1.0];
    [[UIColor blueColor] setStroke];
    [path stroke];
}
@end

上面是自定義的UIView,下面看一下效果

上面在方法- (void)drawRect:(CGRect)rect里面繪制,因為有默認的上下文,如果你想在別的地方進行繪制就要定義新的上下文。

8. - (void)getLineDash:(nullable CGFloat *)pattern count:(nullable NSInteger *)count phase:(nullable CGFloat *)phase;

下面先看一下這幾個參數:

  • pattern:在輸入時,一個C風格的浮點值數組,或者如果不需要模式值,則為nil。 在輸出中,此數組包含線段和間隙的長度(以點為單位)。 數組中的值交替出現,從第一個線段長度開始,接著是第一個間隙長度,接著是第二個線段長度,依此類推。
  • count:輸入時,指向一個整數,如果你不想要整個條目的數量就傳nil。 輸出時,寫入pattern的條目數。
  • phase:輸入時,指向浮點值的指針,如果不需要phase,則為nil。 在輸出中,此值包含開始繪制圖案的偏移量,沿著虛線圖案以點測量。 例如,pattern 5-2-3-2中的phase 值為6將導致繪圖在第一個間隙的中間開始。

pattern參數中的數組必須足夠大,以保存pattern中的所有返回值。 如果您不確定可能有多少個值,則可以調用此方法兩次。 第一次調用它時,不要為pattern傳遞一個值,而是使用count參數中的返回值來分配一個浮點數的數組,然后可以在第二次傳遞過去。


在當前的上下文中操作路徑

主要涉及的是兩個方法

// Path operations on the current graphics context

- (void)fill;
- (void)stroke;

這兩個方法前面一直在用,一個是填充,一個是描邊,就不具體舉例了。


不影響當前上下文的混合模式以及透明度

主要涉及到下面幾個方法

// These methods do not affect the blend mode or alpha of the current graphics context
- (void)fillWithBlendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
- (void)strokeWithBlendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;

- (void)addClip;

1. - (void)strokeWithBlendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;

這里先看一下混合模式這個枚舉

/* Blend modes.

   The blend modes from kCGBlendModeNormal to kCGBlendModeLuminosity are
   supported in Mac OS X 10.4 and later. The Porter-Duff blend modes (from
   kCGBlendModeClear to kCGBlendModePlusLighter) are supported in Mac OS X
   10.5 and later. The names of the Porter-Duff blend modes are historical.

   Note that the Porter-Duff blend modes are not necessarily supported in
   every context. In particular, they are only guaranteed to work in
   bitmap-based contexts, such as those created by CGBitmapContextCreate. It
   is your responsibility to make sure that they do what you want when you
   use them in a CGContext. */

typedef CF_ENUM (int32_t, CGBlendMode) {
    /* Available in Mac OS X 10.4 & later. */
    kCGBlendModeNormal,
    kCGBlendModeMultiply,
    kCGBlendModeScreen,
    kCGBlendModeOverlay,
    kCGBlendModeDarken,
    kCGBlendModeLighten,
    kCGBlendModeColorDodge,
    kCGBlendModeColorBurn,
    kCGBlendModeSoftLight,
    kCGBlendModeHardLight,
    kCGBlendModeDifference,
    kCGBlendModeExclusion,
    kCGBlendModeHue,
    kCGBlendModeSaturation,
    kCGBlendModeColor,
    kCGBlendModeLuminosity,

    /* Available in Mac OS X 10.5 & later. R, S, and D are, respectively,
       premultiplied result, source, and destination colors with alpha; Ra,
       Sa, and Da are the alpha components of these colors.

       The Porter-Duff "source over" mode is called `kCGBlendModeNormal':
         R = S + D*(1 - Sa)

       Note that the Porter-Duff "XOR" mode is only titularly related to the
       classical bitmap XOR operation (which is unsupported by
       CoreGraphics). */

    kCGBlendModeClear,                  /* R = 0 */
    kCGBlendModeCopy,                   /* R = S */
    kCGBlendModeSourceIn,               /* R = S*Da */
    kCGBlendModeSourceOut,              /* R = S*(1 - Da) */
    kCGBlendModeSourceAtop,             /* R = S*Da + D*(1 - Sa) */
    kCGBlendModeDestinationOver,        /* R = S*(1 - Da) + D */
    kCGBlendModeDestinationIn,          /* R = D*Sa */
    kCGBlendModeDestinationOut,         /* R = D*(1 - Sa) */
    kCGBlendModeDestinationAtop,        /* R = S*(1 - Da) + D*Sa */
    kCGBlendModeXOR,                    /* R = S*(1 - Da) + D*(1 - Sa) */
    kCGBlendModePlusDarker,             /* R = MAX(0, (1 - D) + (1 - S)) */
    kCGBlendModePlusLighter             /* R = MIN(1, S + D) */
};

下面我們就看一下代碼

#import "JJCustomView.h"

@implementation JJCustomView

- (void)drawRect:(CGRect)rect
{
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path addArcWithCenter:CGPointMake(200.0, 200.0) radius:100.0 startAngle:0.0 endAngle:M_PI * 2 clockwise:YES];
    CGFloat dashStyle[] = {5.0, 2.0, 3.0, 2.0};
    [path setLineDash:dashStyle count:4 phase:1.0];
    [[UIColor blueColor] setStroke];
    [path strokeWithBlendMode:kCGBlendModeLighten alpha:0.5];
}
@end

看一下實現效果

2. - (void)fillWithBlendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;

還是先看一下代碼。

#import "JJCustomView.h"

@implementation JJCustomView

- (void)drawRect:(CGRect)rect
{
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path addArcWithCenter:CGPointMake(200.0, 200.0) radius:100.0 startAngle:0.0 endAngle:M_PI * 2 clockwise:YES];
    CGFloat dashStyle[] = {5.0, 2.0, 3.0, 2.0};
    [path setLineDash:dashStyle count:4 phase:1.0];
    [[UIColor blueColor] setFill];
    [path fillWithBlendMode:kCGBlendModeLighten alpha:0.3];
}
@end

下面看一下實現效果

效果還是很明顯的。

3. - (void)addClip;

UIBezierPath中的addClip功能,可以實現裁剪:

This method modifies the visible drawing area of the current graphics context. After calling it, subsequent drawing operations result in rendered content only if they occur within the fill area of the specified path.

簡單的說,就是一個path調用addClip之后,它所在的context的可見區(qū)域就變成了它的fill area,接下來的繪制,如果在這個區(qū)域外都會被無視。

將接收器路徑所包圍的區(qū)域與當前圖形上下文的剪切路徑相交,并將生成的形狀作為當前剪切路徑。

此方法修改當前圖形上下文的可見繪圖區(qū)域。 在調用它之后,只有在指定路徑的填充區(qū)域內出現渲染內容時,后續(xù)的繪制操作才會生成渲染內容。

重要:如果需要刪除剪輯區(qū)域以執(zhí)行后續(xù)繪制操作,則必須在調用此方法之前保存當前的圖形狀態(tài)(使用saveGState()函數)。 當不再需要剪切區(qū)域時,可以使用restoreGState()函數恢復以前的圖形屬性和剪切區(qū)域。

usesEvenOddFillRule屬性用于確定是否使用偶數或非零規(guī)則來確定路徑所包含的區(qū)域。

我們先看一段代碼。

#import "JJCustomView.h"

@implementation JJCustomView

- (void)drawRect:(CGRect)rect
{
    UIBezierPath *piePath = [UIBezierPath bezierPath];
    [piePath moveToPoint:CGPointMake(200.0, 200.0)];
    [piePath addArcWithCenter:CGPointMake(200.0, 200.0)  radius:200.0  startAngle:0  endAngle:M_PI * 0.5  clockwise:YES];
    [piePath closePath];
    
    UIBezierPath *anotherPath = [UIBezierPath bezierPath];
    [anotherPath moveToPoint:CGPointMake(50.0, 50.0)];
    [anotherPath addLineToPoint:CGPointMake(250.0, 50.0)];
    
    [piePath appendPath:anotherPath];
    

    [[UIColor blueColor] setStroke];
    [piePath stroke];
}

@end

這個是沒調用- (void)addClip;方法的代碼,看一下輸出效果

可以看見,現在的路徑是扇形加上一條直線,下面我們就調用- (void)addClip;方法,看一下代碼。

#import "JJCustomView.h"

@implementation JJCustomView

- (void)drawRect:(CGRect)rect
{
    UIBezierPath *piePath = [UIBezierPath bezierPath];
    [piePath moveToPoint:CGPointMake(200.0, 200.0)];
    [piePath addArcWithCenter:CGPointMake(200.0, 200.0)  radius:200.0  startAngle:0  endAngle:M_PI * 0.5  clockwise:YES];
    [piePath closePath];
    
    UIBezierPath *anotherPath = [UIBezierPath bezierPath];
    [anotherPath moveToPoint:CGPointMake(50.0, 50.0)];
    [anotherPath addLineToPoint:CGPointMake(250.0, 50.0)];
    
    [piePath appendPath:anotherPath];
    
    [piePath addClip];
    [[UIColor blueColor] setStroke];
    [piePath stroke];
}

@end

下面看一下輸出效果

可以看見橫線沒有了,這是為什么?這是因為[piePath addClip];,那么當前上下文大小就和路徑piePath的fill area填充區(qū)域一樣大了,由于直線是在填充區(qū)域外面,所以被剪切掉了就不會顯示了。相信大家對這個方法的使用已經了解了。

后記

本篇已結束,后續(xù)還會增加相關知識~~~

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容