UIBezierPath

UIBezierPath是什么

  1. 在iPad出现之前,大部分定义绘图只能够使用Core Graphics,因为UIKit并不能绘制任意形状。
  2. 在iOS3.2中添加了UIBezierPath更高级的API来绘制。实际上UIBezierPath是对CGPathRef的封装。
  3. UIKit依然缺乏对渐变、阴影等高级特性的支持。但UIKit却可以非常方便的实现大部分常见的自定义绘制。

UIBezierPath带来的好处

因为是更好级别的API,而不是C语言的层面,所以默认就是ARC,系统帮我们管理内存。而CGPathRef则需要我们自己在合适的时候进行释放内存。从而更专注于绘图。

UIBezierPath怎么使用

  • 自定义View,继承UIView
  • 重写- (void)drawRect:(CGRect)rect方法
  • 创建UIBezierPath
  • 添加路径到UIBezierPath
  • 将UIBezierPath绘制出来

line 直线

1
2
3
4
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(100, 100)];
[path addLineToPoint:CGPointMake(200, 100)];
[path stroke];

blackLine

使用IB_DESIGNABLE

1
2
3
IB_DESIGNABLE
@interface LZLineView : UIView
@end

这样可以直接在storyboard中预览刚刚画出来的直线。

给直线添加颜色

1
[[UIColor redColor] setStroke];

默认是黑色,这里让线条变成红色。

redLine

给直线添加宽度

1
path.lineWidth = 10;

widthLine

总结

可以把UIBezierPath看做是一支画笔,从某个点开始到一个点结束,画笔也有很多种颜色, 画笔也有大小,即宽度。

Bézier curve 贝塞尔曲线

  • 贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计。
  • 贝塞尔曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋。

Quad Curve 二次贝塞尔曲线

附加一个从当前点到结束点的二次贝塞尔曲线到路径。

1
2
3
[path moveToPoint:CGPointMake(200, 200)];
[path addQuadCurveToPoint:CGPointMake(300, 200) controlPoint:CGPointMake(250, 250)];
[path stroke];

效果
Quad Curve

在调用此方法之前:必须设置路径的当前点(方法或通过先前创建直线或曲线段使用moveToPoint)。如果路径是空的,这个方法不起作用。

下图给图了二次贝塞尔曲线的近似值。
图来自于苹果官方文档
Quad Curve Principle

Bézier curve 三次贝塞尔曲线

附加一个三次贝塞尔曲线到路径。

1
2
3
4
5
6
7
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(100, 100)];

[path addCurveToPoint:CGPointMake(250, 100)
controlPoint1:CGPointMake(150, 50)
controlPoint2:CGPointMake(200, 150)];
[path stroke];

curve

- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2
一般与- (void)moveToPoint:(CGPoint)point配合使用。

同二次贝塞尔曲线一样,在调用此方法之前:必须设置路径的当前点(方法或通过先前创建直线或曲线段使用moveToPoint)。如果路径是空的,这个方法不起作用。

起始点、结束点、控制点1、控制点2。两个控制点定义线段(segment)曲率,下图给定了一组初始点的三次贝塞尔曲线近似值,线段的精确曲率涉及所有点之间的复杂数学关系。
图来自于苹果官方文档
curvePrinciple

arc 弧

数学知识

  • 弧度 -> 角度的转换关系:(弧度/π) × 180°
  • 角度 -> 弧度的转换关系:(角度/180) × π

在坐标系统中的角度

图来自于苹果官方文档
Angles in the default coordinate system

实践

1
2
3
UIBezierPath *path = [UIBezierPath bezierPath];    
[path addArcWithCenter:CGPointMake(100, 100) radius:100 startAngle:0 endAngle:M_PI_2 clockwise:YES];
[path stroke];

效果图
arc

参数

参数名 描述
center 原点
radius 圆半径
startAngle 开始角度
endAngle 结束角度
clockwise 绘图方向,默认是顺时针

提供了类方法直接画弧

1
2
3
4
5
6
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 100)
radius:100
startAngle:0
endAngle:M_PI_2
clockwise:YES];
[path stroke];

画矩形

  • (x, y) 决定矩形左上角的点。
  • (width, height) 是矩形的宽度与高度。
1
2
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 100)];
[path stroke];

效果图

Rectangle

画有圆角的矩形

1
[[UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 400, 100, 100) cornerRadius:15] stroke];
  • cornerRadius 值越大,圆角越明显。
  • 如果宽高相等,cornerRadius是宽的一半,那么画出来的就是一个圆。

效果图

rectangleRounded

画有一个或者多个圆角的矩形

1
2
3
[[UIBezierPath bezierPathWithRoundedRect:CGRectMake(250, 200, 100, 100)
byRoundingCorners:UIRectCornerTopLeft
cornerRadii:CGSizeMake(20, 0)] stroke];

效果图

rectangleRoundingCorners

corners 可以传递传递多个值

1
UIRectCornerTopLeft | UIRectCornerTopRight | UIRectCornerBottomLeft |UIRectCornerBottomRight

如果是全部那么就可以直接使用UIRectCornerAllCorners

画椭圆

1
[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 250, 50, 90)] stroke];

效果图

RectangleRound

1
2
3
4
5
// 方式1
[[UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 250, 50, 50)] stroke];

// 方式2
[[UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 400, 100, 100) cornerRadius:50] stroke];

如果宽高相同,那么画出来的就是圆,如下图:
RectangleRound

Stroke与Fill

  • Stroke 沿着路径画线。
  • Fill 绘制当前路径包含的区域。
    效果图

fillStroke

Cap、Join

Cap: Path两端的样式

* kCGLineCapButt  默认值。
* kCGLineCapRound 添加圆形线帽。
* kCGLineCapSquare 添加正方形线帽。

Join: 两个Path连接点的样式

* kCGLineJoinMiter 默认值
* kCGLineJoinRound    连接点的外边缘应该和一个填充的弧接合,这个弧的直径等于线段的宽度
* kCGLineJoinBevel 连接点的外边缘应该和一个填充的三角形相交。

setNeedsDisplay

如果对一个视图调用- (void)setNeedsDisplay方法, 它就被标记为需要刷新,并在下一个绘图中期中重新绘制。只有在视图内容真的会发生变化,才去调用它,一般只有自定义视图才需要调用。因为大部分UIKit视图会在其数据发生变化时自动管理重绘操作。

仿支付宝进度条

仿支付宝下拉刷新的进度条。

  • 先画一个灰色的圆。
1
2
3
4
5
6
7
CGFloat centerX = (rect.size.width - rect.origin.x) * 0.5;
CGFloat centerY = (rect.size.height - rect.origin.y) * 0.5;

UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(centerX, centerY) radius:10 startAngle:0 endAngle:2 * M_PI clockwise:YES];
path.lineWidth = 2;
[[UIColor lightGrayColor] setStroke];
[path stroke];
  • 然后在根据滑动块的值再画一条路径。
1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)setProgressValue:(CGFloat)progressValue {
if (progressValue != _progressValue) {
_progressValue = progressValue;
[self setNeedsDisplay]; // 重绘
}
}

CGFloat startAngle = -M_PI_2;
CGFloat endAngle = startAngle + self.progressValue * M_PI * 2;
UIBezierPath *progreePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(centerX, centerY) radius:10 startAngle:startAngle endAngle:endAngle clockwise:YES];
progreePath.lineWidth = 2;
[[UIColor blueColor] setStroke];
[progreePath stroke];

效果图:
progress

参考

源代码

目前iOS10正式版还没有发布,后面会新增Swift3.0的使用。
源码地址:
Quartz2D

坚持原创技术分享,您的支持将鼓励我继续创作!