iOS UIBezierPath使用——贝塞尔曲线

UIBezierPath用于定义一个直线/曲线组合而成的路径,并且可以在自定义视图中渲染该路径。

注意:使用UIBezierPath绘画的代码写在自定义视图的drawRect:方法中。

一、创建UIBezierPath.

+ (instancetype)bezierPath;

初始化一个UIBezierPath对象。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

+ (instancetype)bezierPathWithRect:(CGRect)rect;

以CGRect为范围,创建一个矩形路径。

UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 200, 300)];

+ (instancetype)bezierPathWithOvalInRect:(CGRect)rect;

以CGRect为范围,创建一个圆/椭圆。

UIBezierPath *bezierPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 200, 300)];

+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;

以CGRect为大小,以cornerRadius为圆角半径,绘制一个圆角矩形。

UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 200, 300) cornerRadius:10.f];

+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;

绘制一个圆角矩形,通过UIRectCorner选择圆弧的位置,cornerRadii为圆弧的大小。

注:UIRectCorner的值:

typedefNS_OPTIONS(NSUInteger, UIRectCorner) {

    UIRectCornerTopLeft    =1<<0,                        //矩形左上角

    UIRectCornerTopRight    =1<<1,                       //矩形右上角

    UIRectCornerBottomLeft  =1<<2,                    //矩形左下角

    UIRectCornerBottomRight =1<<3,                   //矩形右下角

    UIRectCornerAllCorners  = ~0UL                     //矩形四个角都包括

};

注意:cornerRadii为圆弧半径,圆弧以cornerRadii宽、高的值大的为准,如果超过其邻近最短边的一半,则已最短边一半为准。(自己试出来的)

UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 200, 300) byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight | UIRectCornerBottomLeft cornerRadii:CGSizeMake(20, 40)];

+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;

绘制一个圆弧路径。ArcCenter:圆弧圆心位置;radius:圆弧半径;startAngle:开始的弧度(角度);endAngle:结束的弧度(角度);clockwise:是否为顺时针。

注意:iPhone中,左上角为原点,x轴向右为正方向;y轴向下为正方向。

UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:100 startAngle:M_PI_2 endAngle: 2 * M_PI clockwise:YES];

+ (instancetype)bezierPathWithCGPath:(CGPathRef)CGPath;

根据CGPath创建一个新的UIBezierPath。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

UIBezierPath *newPath = [UIBezierPath bezierPathWithCGPath:bezierPath.CGPath];

二、路径操作函数

- (void)moveToPoint:(CGPoint)point;

将当前点移动到指定的位置。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath moveToPoint:CGPointMake(100, 300)];

- (void)addLineToPoint:(CGPoint)point;

在路径中增加一条直线(起点+终点=一条直线)

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath moveToPoint:CGPointMake(100,300)];

[bezierPath addLineToPoint:CGPointMake(150, 150)];

- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;

绘制三阶贝塞尔曲线。以endPoint为终点,以controlPoint1、controlPoint2两个点为控制点,绘制贝塞尔曲线。

三阶贝塞尔曲线原理图

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath moveToPoint:CGPointMake(100,300)];

[bezierPath addCurveToPoint:CGPointMake(300, 300) controlPoint1:CGPointMake(150, 150) controlPoint2:CGPointMake(220, 360)];

三阶贝塞尔曲线

- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;

绘制二阶贝塞尔曲线。以endPoint为终点,以controlPoint为控制点,绘制二阶贝塞尔曲线。

二阶贝塞尔曲线原理图

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath moveToPoint:CGPointMake(100,300)];

[bezierPath addQuadCurveToPoint:CGPointMake(300, 300) controlPoint:CGPointMake(150, 150)];

二阶贝塞尔曲线

- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise NS_AVAILABLE_IOS(4_0);

绘制一条圆弧。ArcCenter:圆弧圆心位置;radius:圆弧半径;startAngle:开始的弧度(角度);endAngle:结束的弧度(角度);clockwise:是否为顺时针。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath moveToPoint:CGPointMake(100,300)];

[bezierPath addArcWithCenter:self.view.center radius:100 startAngle:M_PI_2 endAngle:2 * M_PI clockwise:YES];

注:因为圆弧的中心和起点不是一个位置,所以效果图中多了一个从起点到圆弧开始点的直线。

效果图

- (void)closePath;

使用一条直线闭合路径的起点和终点, 该方法同时也会更新当前点到新直线的终点(即路径的起点)(使一个路径变成闭合回路)。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath moveToPoint:CGPointMake(100,300)];

[bezierPath addLineToPoint:CGPointMake(150, 50)];

[bezierPath addLineToPoint:CGPointMake(220, 80)];

[bezierPath closePath];

[bezierPath addLineToPoint:CGPointMake(250, 430)];

注:在效果图中可以看出,在调用closePath方法之后,路径形成了一个封闭的三角形,之后再添加直线,也是从起点开始,而不是上一个终点。

效果图

- (void)removeAllPoints;

移除路径中所有的点。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath removeAllPoints];

- (void)appendPath:(UIBezierPath *)bezierPath;

路径中增加一个已有路径。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

UIBezierPath *path2 = [UIBezierPath bezierPath];

[bezierPath appendPath:path2];

- (UIBezierPath *)bezierPathByReversingPath NS_AVAILABLE_IOS(6_0);

返回一个翻转已有路径的新路径。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

UIBezierPath*path2 = [bezierPath bezierPathByReversingPath];

- (void)applyTransform:(CGAffineTransform)transform;

对路径中的所有点进行二维形变, 该变化立即生效, 且为永久性改变所有点。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath applyTransform:CGAffineTransformMakeTranslation(20, 20)];

三、路径信息属性

@property(nonatomic) CGPathRef CGPath;

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

CGPathRefpath = bezierPath.CGPath;

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

是否路径信息为空, 即使通过moveToPoint:移动到指定的位置也算不为空。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

BOOLisEmpty = bezierPath.empty;

@property(nonatomic,readonly) CGRect bounds;

可以封闭所有路径点的最小矩形范围, 包括多次贝塞尔曲线的控制点在内。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

CGRect bounds = bezierPath.bounds;

@property(nonatomic,readonly) CGPoint currentPoint;

路径当前所在点。

UIBezierPath*path = [UIBezierPathbezierPath];

CGPoint currentPoint = path.currentPoint;

- (BOOL)containsPoint:(CGPoint)point;

是否包含指定点。

UIBezierPath*path = [UIBezierPathbezierPath];

BOOL isContainsPoint = [path containsPoint:CGPointMake(20,30)];

四、绘图相关方法和属性

@property(nonatomic) CGFloat lineWidth;

路径的线宽。

UIBezierPath*path = [UIBezierPath bezierPath];

path.lineWidth =10;

@property(nonatomic) CGLineCap lineCapStyle;

路径的终点形状, 该属性适用于开放路径的起点和终点。

注意:lineCapStyle的值:

typedefCF_ENUM(int32_t, CGLineCap) {

    kCGLineCapButt,                //方形结束, 结束位置正好为精确位置。——默认值

    kCGLineCapRound,            //圆形结束, 结束位置超过精确位置半个线宽。

    kCGLineCapSquare            //方形结束, 结束位置超过精确位置半个线宽。

};

UIBezierPath*path = [UIBezierPath bezierPath];

path.lineCapStyle = kCGLineCapButt;

@property(nonatomic) CGLineJoin lineJoinStyle;

路径的连接点(拐角)形状。

注意:lineJoinStyle的值:

typedefCF_ENUM(int32_t, CGLineJoin) {

    kCGLineJoinMiter,                //全部连接(尖角)。——默认值

    kCGLineJoinRound,              //圆形连接。(圆角)

    kCGLineJoinBevel                //斜角连接。(切角)

};

拐角样式

UIBezierPath*path = [UIBezierPath bezierPath];

path.lineJoinStyle = kCGLineJoinMiter;

@property(nonatomic) CGFloat miterLimit; // Used when lineJoinStyle is kCGLineJoinMiter

最大斜接长度,怎么理解呢?就是上图中拐角处外面尖角和内部尖角的距离。但是这个只有在kCGLineJoinMiter情况下使用才有效果,如果这个miterLimit小于斜接长度,就成为了kCGLineJoinBevel类型。

UIBezierPath*path = [UIBezierPath bezierPath];

path.lineJoinStyle = kCGLineJoinMiter;

path.miterLimit = 1;                ////这里设为1 因为斜接长度超过了1 所以就自动转化为了kCGLineJoinBevel类型。

@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.

一个bool值指定even-odd规则是否在path可用。

- (void)setLineDash:(nullableconstCGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase;

设置线型 可设置成虚线。

注:pattern 线段数组 如:CGFloat dash[] = {1,1}; 代表实线和空白交替的长度 及先绘制1个长度再空1个,再绘制一个.....;

        count数组长度 count值小于数组实际长度时,方法就会对相应长度的数组元素进行循环,而大于的时候 会有警告,没有效果;                           

        phase 循环数组时跳过的长度,如pattern为{2,6},phase为1,则第一个元素画1的时候就跳过直接画6

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath moveToPoint:CGPointMake(100,300)];

[bezierPath addLineToPoint:CGPointMake(150, 50)];

CGFloat patten[] = {4,6};

[bezierPath setLineDash:pattencount:2phase:1];

效果图

- (void)getLineDash:(nullableCGFloat *)pattern count:(nullableNSInteger *)count phase:(nullableCGFloat *)phase;

检索线型。

- (void)fill;

利用当前绘画属性填充路径封闭范围, 该方法在绘画之前会自动将开放子路径封闭, 填充部分不包含路径本身, 所以对于线宽较大的路径, 填充部分会跟部分路径重合。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath moveToPoint:CGPointMake(100,300)];

[bezierPath addLineToPoint:CGPointMake(150, 50)];

[bezierPath addLineToPoint:CGPointMake(220, 80)];

bezierPath.lineWidth=5.0;

[bezierPath fill];

[bezierPath stroke];

- (void)stroke;

利用当前绘画属性沿着路径画线。

UIBezierPath*path = [UIBezierPathbezierPath];

// do something....

[path stroke];

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

利用指定模式填充路径封闭范围, 该方法在绘画之前会自动将开放子路径封闭, 填充部分不包含路径本身, 所以对于线宽较大的路径, 填充部分会跟部分路径重合。

注意:CGBlendMode的值很多,大家有兴趣的可以自己挨个试试。

typedefCF_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) */

};

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath moveToPoint:CGPointMake(100,300)];

[bezierPath addLineToPoint:CGPointMake(150, 50)];

[bezierPath addLineToPoint:CGPointMake(220, 80)];

bezierPath.lineWidth=5.0;

[bezierPath fillWithBlendMode:kCGBlendModeNormal alpha:0.4];

[bezierPath stroke];

效果图

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

利用指定模式沿着路径画线。

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath moveToPoint:CGPointMake(100,300)];

[bezierPath addLineToPoint:CGPointMake(150, 50)];

[bezierPath addLineToPoint:CGPointMake(220, 80)];

bezierPath.lineWidth=5.0;

[bezierPath strokeWithBlendMode:kCGBlendModeNormal alpha:0.4];

效果图

- (void)addClip;

剪切被接收者路径包围的区域该路径是带有剪切路径的当前绘图上下文。使得其成为我们当前的剪切路径。简单的说,就是一个path调用addClip之后,它所在的context的可见区域就变成了它的“fill area”,接下来的绘制,如果在这个区域外都会被无视。

//首先画一个三角形

UIBezierPath *bezierPath = [UIBezierPath bezierPath];

[bezierPath moveToPoint:CGPointMake(100,300)];

[bezierPath addLineToPoint:CGPointMake(150, 50)];

[bezierPath addLineToPoint:CGPointMake(220, 80)];

bezierPath.lineWidth=5.0;

[bezierPath fillWithBlendMode:kCGBlendModeNormal alpha:0.4];

//然后调用addClip(裁剪)方法。

[bezierPath addClip];

//之后,在绘制一个圆弧。

[bezierPath addArcWithCenter:bezierPath.currentPoint radius:100 startAngle:M_PI_2 endAngle:2 * M_PI clockwise:YES];

[bezierPath stroke];

效果图上可以看出圆弧只显示了在三角形上的一部分。

效果图

- (void)setFill;

路径的填充颜色。

[[UIColor orangeColor] setFill];

- (void)setStroke;

路径的画线颜色。

[[UIColor orangeColor] setStroke];

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容