TopicList
- 一, UIBezierPath 简介
- 二, UIBezierPath 初始化方法
- 三, UIBezierPath 常用属性说明
- 四, UIBezierPath 构建Path方法
- 五, 图形上下文中的路径操作
- 六, 由简入繁绘制贝塞尔曲线
- 七, 报错等相关信息说明
- 八, 引用文献说明
话说,入坑得从官方文档文档开始:
The UIBezierPath 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一次贝塞尔曲线只是一条两点之间的直线。这条线由下式给出:
一次贝塞尔曲线函数中的t会经由起始点P0至终止点P1的B(t)所描述的曲线。例如当t=0.25时,B(t)即一条由点P0至P1路径的四分之一处。就像由0至1的连续t,B(t)描述一条由P0至P1的直线.
二次贝塞尔曲线
二次方贝塞尔曲线的路径由给定点P0、P1、P2的函数B(t)追踪:
二次贝塞尔曲线,为建构二次贝塞尔曲线,需要两个控制点Q0和Q1作为由0至1的控制:
由P0至P1的控制点Q0,描述一条线性贝塞尔曲线;
由P1至P2的控制点Q1,描述一条线性贝塞尔曲线;
由Q0至Q1的连续点B(t),描述一条二次贝塞尔曲线;
三次贝塞尔曲线
对于三次曲线,可由线性贝塞尔曲线描述的中介点Q0、Q1、Q2,和由二次曲线描述的点R0、R1所建构:
更多关于贝塞尔曲线的介绍,可参考维基百科的相关介绍
二, 初始化方法
// 初始化方法,需要用实例方法添加线条.使用比较多,可以根据需要任意定制样式,画任何我们想画的图形.
+ (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;
六, 由简入繁绘制贝塞尔曲线
- 初始化一个 UIBezierPath 对象
- 设置相关的属性;
- 调用
-moveToPoint:
方法设置线段的起点; - 添加线段或者曲线段去构建一个或者多个子路径;
代码示例:
#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];
}
效果预览:
七, 报错等相关信息说明
1.关于drawRext: 如果你是在控制器的 viewDidLoad
方法中绘制的,那么将得到如下的报错信息:
打印报错:CGContextSaveGState:invalid context 0x0.CGContextDrawPath: invalid context 0x0.
原因:没有在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.报错信息
解决办法: H含金量 iOS绘图及贝塞尔曲线关键知识
3.角度控制
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