iOS UIBezierPath(贝塞尔曲线)

UIBezierPath.jpg

TopicList

  • 一, UIBezierPath 简介
  • 二, UIBezierPath 初始化方法
  • 三, UIBezierPath 常用属性说明
  • 四, UIBezierPath 构建Path方法
  • 五, 图形上下文中的路径操作
  • 六, 由简入繁绘制贝塞尔曲线
  • 七, 报错等相关信息说明
  • 八, 引用文献说明

话说,入坑得从官方文档文档开始:

The UIBezier​Path class lets you define a path consisting of straight and curved line segments and render that path in your custom views. You use this class initially to specify just the geometry for your path. Paths can define simple shapes such as rectangles, ovals, and arcs or they can define complex polygons that incorporate a mixture of straight and curved line segments. After defining the shape, you can use additional methods of this class to render the path in the current drawing context.

UIBezierPath类允许您定义一个由直线和曲线组成的路径在您的自定义视图中。你使用这个类的初始化方法指定的几何路径。路径可以是简单的几何形状,例如:矩形、椭圆、弧,也可以定义为复杂的多边形,或者说直线和曲线段的混合形状。形状定义之后,您可以使用这个类的其他方法来呈现当前绘图的上下文路径。

我们可以使用直线段去创建矩形和多边形,使用曲线段去创建弧(arc),圆或者其他复杂的曲线形状.曲线定义: 起始点,终止点(锚点),控制点.通过调整控制点,贝塞尔曲线会发生变化.

一, UIBezierPath 简介

UIBezierPath(贝塞尔曲线),位于 UIKit 框架,使用此类可以定义简单的形状,如椭圆或者矩形,或者有多个直线和曲线段组成的形状.我们可以使用直线段去创建矩形和多边形,使用曲线段去创建弧(arc),圆或者其他复杂的曲线形状. 曲线定义: 起始点,终止点(锚点),控制点.通过调整控制点,贝塞尔曲线会发生变化.

贝塞尔曲线的数理知识
一次贝塞尔曲线

给定点P0,P1一次贝塞尔曲线只是一条两点之间的直线。这条线由下式给出:

01.jpeg

一次贝塞尔曲线函数中的t会经由起始点P0至终止点P1的B(t)所描述的曲线。例如当t=0.25时,B(t)即一条由点P0至P1路径的四分之一处。就像由0至1的连续t,B(t)描述一条由P0至P1的直线.
一次贝塞尔曲线演示动画.gif

二次贝塞尔曲线

二次方贝塞尔曲线的路径由给定点P0、P1、P2的函数B(t)追踪:


02.jpeg

二次贝塞尔曲线,为建构二次贝塞尔曲线,需要两个控制点Q0和Q1作为由0至1的控制:
由P0至P1的控制点Q0,描述一条线性贝塞尔曲线;
由P1至P2的控制点Q1,描述一条线性贝塞尔曲线;
由Q0至Q1的连续点B(t),描述一条二次贝塞尔曲线;


二次贝塞尔曲线演示动画.gif
三次贝塞尔曲线

对于三次曲线,可由线性贝塞尔曲线描述的中介点Q0、Q1、Q2,和由二次曲线描述的点R0、R1所建构:


03.jpeg
三次贝塞尔曲线动画演示.gif

更多关于贝塞尔曲线的介绍,可参考维基百科的相关介绍

二, 初始化方法
 // 初始化方法,需要用实例方法添加线条.使用比较多,可以根据需要任意定制样式,画任何我们想画的图形.
 + (instancetype)bezierPath;
 
 // 返回一个矩形 path
 + (instancetype)bezierPathWithRect:(CGRect)rect;
 
 // 返回一个圆形或者椭圆形 path
 + (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
 
 // 返回一个带圆角的矩形 path ,矩形的四个角都是圆角;
 + (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;
 
 // 返回一个带圆角的矩形 path , UIRectCorner 枚举值可以设置只绘制某个圆角;
 + (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
 
 // 返回一段圆弧,参数说明: center: 弧线中心点的坐标 radius: 弧线所在圆的半径 startAngle: 弧线开始的角度值 endAngle: 弧线结束的角度值 clockwise: 是否顺时针画弧线.
 + (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
 
 // 用一条 CGpath 初始化
 + (instancetype)bezierPathWithCGPath:(CGPathRef)CGPath;
 
 // 返回一个反转当前路径的路径对象.(反方向绘制path)
 - (UIBezierPath *)bezierPathByReversingPath;
三, UIBezierPath 常用属性

1.CGPath: 将UIBezierPath类转换成CGPath
2.currentPoint: 当前path的位置,可以理解为path的终点
3.lineWidth: 线条宽度
4.lineCapStyle: 端点样式
5.lineJoinStyle: 连接类型
6.flatness: 绘线的精细程度,默认为0.6,数值越大,需要处理的时间越长
7.usesEvenOddFillRule: 判断奇偶数组的规则绘制图像,图形复杂时填充颜色的一种规则。类似棋盘
8.miterLimit: 最大斜接长度(只有在使用kCGLineJoinMiter是才有效,最大限制为10), 边角的角度越小,斜接长度就会越大,为了避免斜接长度过长,使用lineLimit属性限制,如果斜接长度超过miterLimit,边角就会以KCALineJoinBevel类型来显示
9.- setLineDash:count:phase:为path绘制虚线,dash数组存放各段虚线的长度,count是数组元素数量,phase是起始位置

 // lineCapStyle     // 端点类型
 kCGLineCapButt,     // 无端点
 kCGLineCapRound,    // 圆形端点
 kCGLineCapSquare    // 方形端点(样式上和kCGLineCapButt是一样的,但是比kCGLineCapButt长一点)
 
 // lineJoinStyle     // 交叉点的类型
 kCGLineJoinMiter,    // 尖角衔接
 kCGLineJoinRound,    // 圆角衔接
 kCGLineJoinBevel     // 斜角衔接
四, UIBezierPath 构建Path
 - (void)moveToPoint:(CGPoint)point;
 // 以 point点 开始作为起点, 一般用`+ (instancetype)bezierPath`创建的贝塞尔曲线,先用该方法标注一个起点,再调用其他的创建线条的方法来绘制曲线
 
 // 绘制二次贝塞尔曲线的关键方法,即从path的最后一点开始添加一条线到point点
 - (void)addLineToPoint:(CGPoint)point;
 
 // 绘制二次贝塞尔曲线的关键方法,和`-moveToPoint:`配合使用. endPoint为终止点,controlPoint为控制点.
 - (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
 
 // 绘制三次贝塞尔曲线的关键方法,以三个点画一段曲线. 一般和moveToPoint:配合使用.
 // 其中,起始点由`-moveToPoint:`设置,终止点位为`endPoint:`, 控制点1的坐标controlPoint1,控制点2的坐标是controlPoint2.
 - (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
 
 // 绘制一段圆弧, center:原点坐标,radius:半径,startAngle:起始角度,endAngle:终止角度,clockwise顺时针/逆时针方向绘制
 - (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
 
 // 闭合线
 - (void)closePath;
 
 // 移除所有的点,从而有效地删除所有子路径
 - (void)removeAllPoints;
 
 // 追加指定的bezierPath到路径上
 - (void)appendPath:(UIBezierPath *)bezierPath;
 
 // 用仿射变换矩阵变换路径的所有点
 - (void)applyTransform:(CGAffineTransform)transform;
五, 图形上下文中的路径操作
 // 填充路径
 - (void)fill;
 
 // 各个点连线
 - (void)stroke;
 
 // 填充模式, alpha 设置
 // blendMode : https://onevcat.com/2013/04/using-blending-in-ios/
 - (void)fillWithBlendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
 
 // 链接模式, alpha 设置
 - (void)strokeWithBlendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
 
 // 图形绘制超出当前路径范围,则不可见
 - (void)addClip;
六, 由简入繁绘制贝塞尔曲线
  1. 初始化一个 UIBezierPath 对象
  2. 设置相关的属性;
  3. 调用 -moveToPoint: 方法设置线段的起点;
  4. 添加线段或者曲线段去构建一个或者多个子路径;

代码示例:

#define kLineW ScreenWidth-10*2
#define kMargin 10

// 重写 drawRect 方法
- (void)drawRect:(CGRect)rect {
    
    //由于UIBezierPath绘制出来的是矢量图形(即layer路径)并不能真正的展示出来,因此,想让它显示在图层上,需要设置线条颜色
    [[UIColor orangeColor] set];
    
    // 示例代码:
    // 1. 绘制一条直线,即一次贝塞尔曲线
    UIBezierPath *path = [[UIBezierPath alloc] init];
    path.lineWidth = 1.f;
    path.lineCapStyle = kCGLineCapRound;
    path.lineJoinStyle = kCGLineCapRound;
    path.miterLimit = 10.f;
    path.flatness = 10.f;
    path.usesEvenOddFillRule = YES;
    // 设置起始点
    [path moveToPoint:CGPointMake(kMargin, kMargin)];
    // 添加子路径
    [path addLineToPoint:CGPointMake(kLineW, kMargin)];//添加一条子路径
    // 根据坐标点连线
    [path stroke];
    
    // 2.绘制一条折线,其实就是增加一个端点
    UIBezierPath *path1 = [UIBezierPath bezierPath];
    [path1 moveToPoint:CGPointMake(kMargin, kMargin*2)];
    [path1 addLineToPoint:CGPointMake(kLineW, kMargin*2)];//添加一条子路径
    [path1 addLineToPoint:CGPointMake(kLineW, kMargin*3)];//添加两条子路径
    [path1 closePath];//当构建子路径数>=2条时,可以调用`closePath`方法来闭合路径.
    [path1 stroke];
    
    // 3.绘制一个矩形
    // 方法1: 类似上面,用点去绘制;
    UIBezierPath *path2 = [UIBezierPath bezierPath];
    [path2 moveToPoint:CGPointMake(kMargin, kMargin*4)];
    [path2 addLineToPoint:CGPointMake(kLineW, kMargin*4)];
    [path2 addLineToPoint:CGPointMake(kLineW, kMargin*5)];
    [path2 addLineToPoint:CGPointMake(kMargin, kMargin*5)];
    [path2 stroke];
    
    // 方法2: 初始化方法直接绘制
    UIBezierPath *path3 = [UIBezierPath bezierPathWithRect:CGRectMake(kMargin, kMargin*6, kLineW, kMargin*5)];
    [path3 fill];// 设置填充
    [path3 stroke];
    
    // 4. 绘制带圆角的矩形
    UIBezierPath *path4 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(kMargin, kMargin*13, kLineW, kMargin*5) byRoundingCorners:UIRectCornerTopLeft | UIRectCornerBottomRight cornerRadii:CGSizeMake(kMargin, kMargin)];
    [path4 fillWithBlendMode:kCGBlendModeMultiply alpha:0.3];
    [path4 stroke];
    
    // 5. 绘制椭圆
    UIBezierPath *path5 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(self.center.x-100, kMargin*20, 200, 50)];
    [path5 fillWithBlendMode:kCGBlendModeOverlay alpha:0.5];
    [path5 stroke];
    
    // 6. 绘制圆形
    UIBezierPath *path6 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(kMargin*5, kMargin*26, 100, 100)];
    [path6 stroke];
    
    // 7. 绘制一段圆弧
    UIBezierPath *path7 = [UIBezierPath bezierPathWithArcCenter:CGPointMake(kMargin*30, kMargin*31) radius:50 startAngle:1.5*3.1415926 endAngle:3.1415926 clockwise:YES];
    [path7 stroke];
    
    // 8.绘制扇形
    UIBezierPath * path8 = [UIBezierPath bezierPath]; // 创建路径
    [path8 moveToPoint:CGPointMake(100, kMargin*45)]; // 设置起始点
    [path8 addArcWithCenter:CGPointMake(100, kMargin*45) radius:50 startAngle:0 endAngle:3.14159/2 clockwise:NO];
    //[[UIColor lightGrayColor] setStroke];
    //[[UIColor lightGrayColor] setFill];
    [path8 closePath];
    [path8 stroke];
    
    // 9. 绘制竖直虚线
    UIBezierPath *verticalLinePath = [UIBezierPath bezierPath];
    CGFloat dash[] = {3.0, 3.0};
    [verticalLinePath setLineDash:dash count:2 phase:0.0];
    [verticalLinePath moveToPoint: CGPointMake(5, 0)];
    [verticalLinePath addLineToPoint: CGPointMake(5, ScreenHeight*2)];
    [verticalLinePath stroke];
    [verticalLinePath fill];
    
    // 10.绘制二次贝塞尔曲线
    UIBezierPath *path9 = [UIBezierPath bezierPath];
    [path9 moveToPoint:CGPointMake(250, 450)];
    [path9 addQuadCurveToPoint:CGPointMake(350, 450) controlPoint:CGPointMake(300, 550)];
    [path9 stroke];
    
    // 11.绘制三次贝塞尔曲线
    UIBezierPath *path10 = [UIBezierPath bezierPath];
    [path10 moveToPoint:CGPointMake(50, 550)];
    [path10 addCurveToPoint:CGPointMake(300, 550) controlPoint1:CGPointMake(150, 450) controlPoint2:CGPointMake(250, 600)];
    [path10 stroke];
}

效果预览:

效果预览.png
七, 报错等相关信息说明

1.关于drawRext: 如果你是在控制器的 viewDidLoad 方法中绘制的,那么将得到如下的报错信息:
打印报错:CGContextSaveGState:invalid context 0x0.CGContextDrawPath: invalid context 0x0.

QQ20170325-161232.png

原因:没有在view-drawRext:方法中进行相关的操作:
例如 UIBezierPath- (void)setFill, - (void)setStroke 方法, UIColor- (void)setFill, - (void)setStroke方法等

解决办法:

  • 把有关 view layer 层的操作单独封装到一个 view 类的 -drawRect: 方法中
  • 不要调用相关的方法,一般官方文档会给予提示:
    Set the fill or stroke colors individually. These should be implemented by subclassers.

2.报错信息

QQ20170330-113227.png

解决办法: H含金量 iOS绘图及贝塞尔曲线关键知识

3.角度控制

角度控制.png

4.关于颜色
由于UIBezierPath绘制出来的是矢量图形(即layer路径)并不能真正的展示出来,因此,想让它显示在图层上,需要设置线条颜色,有以下方法:

1.直接设置 color
[[UIColor orangeColor] set];

2. 和 CAShapeLayer 搭配使用, shape外形,顾名思义就是用来呈现layer外形的.
UIBezierPath *pathA = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 100)];
CAShapeLayer *layerA = [[CAShapeLayer alloc] init];
// 设置线条宽度
layerA.lineWidth = 1.f;
// 线条填充色
layerA.strokeColor = [UIColor orangeColor].CGColor;
// 背景填充色
layerA.fillColor = [UIColor whiteColor].CGColor;
// 设置路径
layerA.path = path.CGPath;
[self.layer addSublayer:layerA];

3.常用代码段

/// 画圆点
CGPoint point = [self.pointArray.firstObject CGPointValue];
CGRect frame = CGRectMake(point.x-4, point.y-4, 8, 8);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:frame];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path.CGPath;
shapeLayer.fillColor = RGB(8, 202, 203).CGColor;
shapeLayer.shadowOffset = CGSizeMake(0,4);
shapeLayer.shadowOpacity = 0.3;
[self.layer addSublayer:shapeLayer];
八, 引用文献说明

wikiwand
UIBezierPath 学习笔记
UIBezierPath 通过贝塞尔曲线画圆环 创建一个环形进度指示器
CAShapeLayer

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

推荐阅读更多精彩内容