CGPathRef(CGPath)

CGPath, CGPathRef, UIBezierPath 三个实际上都是一回事。

Line

画线

1
2
3
4
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 10, 150);
CGContextAddLineToPoint(context, 100, 150);
CGContextStrokePath(context);

设置Line的颜色

1
2
3
4
// 方式1
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
// 方式2
CGContextSetRGBStrokeColor(context, 0.5, 0.7, 1.0, 1.0);

设置填充颜色

1
2
3
4
// 方式1
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
// 方式2
CGContextSetRGBFillColor(context, .09, 0.0, 0.0, 1.0);

添加多条线

1
2
3
4
5
6
7
8
9
10
CGPoint addLines[] = {
CGPointMake(10, 120),
CGPointMake(70, 80),
CGPointMake(130, 120),
CGPointMake(190, 80),
CGPointMake(250, 120),
CGPointMake(310, 80),
};
CGContextAddLines(context, addLines, 6);
CGContextStrokePath(context);

相当于多次调用

1
2
3
4
5
CGContextMoveToPoint(context, addLines[0].x, addLines[0].y);
for (int i = 1; i < 6; ++i) {
CGContextAddLineToPoint(context, addLines[i].x, addLines[i].y);
}
CGContextStrokePath(context);

画线段

1
2
3
4
5
CGPoint strokeSegments[] = {
CGPointMake(10.0, 250.0),
CGPointMake(70.0, 220.0)
};
CGContextStrokeLineSegments(context, strokeSegments, 2);

画多条线段

1
2
3
4
5
6
7
8
9
CGPoint strokeSegments[] = {
CGPointMake(10.0, 250.0),
CGPointMake(70.0, 220.0),
CGPointMake(130.0, 250.0),
CGPointMake(190.0, 220.0),
CGPointMake(250.0, 250.0),
CGPointMake(310.0, 220.0),
};
CGContextStrokeLineSegments(context, strokeSegments, 6);

相当于多次调用

1
2
3
4
5
for (int i = 0; i < 6; i +=2) {
CGContextMoveToPoint(context, strokeSegments[i].x, strokeSegments[i].y);
CGContextAddLineToPoint(context, strokeSegments[i+1].x, strokeSegments[i+1].y);
CGContextStrokePath(context);
}

Cap、Join、Width

说明参考
UIBezierPath

1
2
CGContextSetLineWidth(context, self.width);
CGContextSetLineJoin(context, self.join);

Graphics State

Quartz2D简述

CGContextSetLineDash 画虚线

1
2
3
4
5
6
void CGContextSetLineDash (
CGContextRef c,
CGFloat phase,
const CGFloat *lengths,
size_t count
);

lengths

  • lengths 值的数组指定虚线样式
  • count 一般而言等于lengths数组的长度
1
2
3
4
5
6
7
8
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat lengths[] = {10, 10};
CGContextSetLineDash(context, 0, lengths, 2);
CGContextMoveToPoint(context, 10, 100);
CGContextAddLineToPoint(context, 300, 100);
CGContextStrokePath(context);

lengths10_10

1
CGFloat lengths[] = {10, 30};

lengths10_30

1
CGFloat lengths[] = {10, 30, 20};

lengths10_30_20

结论
在数组中,根据数组的长度

  • 例如{10} 先绘制10个点,在空10个点, 返回执行。
  • 例如{10, 20} 先绘制10个点,再空20个点,反复执行,直到视图的最大值。
  • 例如{10, 30, 20} 先绘制10个点、再空30个点,再绘制20个点,再空10个点,返回执行,知道视图的最大值。
  • ….

phase

这个字段更有意思了,

1
2
CGFloat lengths[] = {30, 10};
CGContextSetLineDash(context, 10, lengths, 2);

phase30_10

1
CGContextSetLineDash(context, 20, lengths, 2);

phase30_20

结论
lengths[] = {30, 10}

  • phase = 10, 先绘制 30 - 10个点,在空10格点,再绘制30个点,反复执行,直到视图的最大值。
  • phase = 10, 先绘制 30 - 20 个点,在空10格点,再绘制30个点,反复执行,直到视图的最大值。

Quad curve 二次贝塞尔曲线

1
2
3
4
5
6
start = CGPointMake(30.0, 300.0);
end = CGPointMake(270.0, 300.0);
cp1 = CGPointMake(150.0, 180.0);
CGContextMoveToPoint(context, start.x, start.y);
CGContextAddQuadCurveToPoint(context, cp1.x, cp1.y, end.x, end.y);
CGContextStrokePath(context);

Quad Curve

Bézier curve 三次贝塞尔曲线

1
2
3
4
5
6
7
8
9
10
CGContextSetLineWidth(context, 2.0);
CGPoint start = CGPointMake(30, 150);
CGPoint end = CGPointMake(250, 150);
CGPoint cp1 = CGPointMake(85, 60);
CGPoint cp2 = CGPointMake(140, 240);
CGContextMoveToPoint(context, start.x, start.y);
CGContextAddCurveToPoint(context, cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
CGContextStrokePath(context);

curve

矩形

画一个空心矩形

1
2
CGContextAddRect(context, CGRectMake(30, 80, 60, 60));
CGContextStrokePath(context);

画一个空心矩形便利方法

1
CGContextStrokeRect(context, CGRectMake(30, 150, 60, 60));

相当于CGContextAddRect + CGContextStrokePath

画多个空心矩形

1
2
3
4
5
6
7
8
CGRect rects[] = {
CGRectMake(120.0, 80.0, 60.0, 60.0),
CGRectMake(120.0, 170.0, 60.0, 60.0),
CGRectMake(120.0, 280.0, 60.0, 60.0),
};
CGContextAddRects(context, rects, 3);
CGContextStrokePath(context);

画一个空心矩形,指定画笔的宽度

1
CGContextStrokeRectWithWidth(context, CGRectMake(30.0, 240.0, 60.0, 60.0), 10);
  • 改变画笔颜色,再画一个矩形覆盖在之前的矩形上。
1
2
3
4
CGContextSaveGState(context);
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextStrokeRectWithWidth(context, CGRectMake(30.0, 240.0, 60.0, 60.0), 2.0);
CGContextRestoreGState(context);

画一个填充矩形

1
2
CGContextAddRect(context, CGRectMake(210.0, 80.0, 60.0, 60.0));
CGContextFillPath(context);

画一个填充矩形便利方法

1
CGContextFillRect(context, CGRectMake(210.0, 160.0, 60.0, 60.0));

画多个填充矩形

1
2
3
4
5
6
7
CGRect fillRects[] = {
CGRectMake(280.0, 80.0, 60.0, 60.0),
CGRectMake(280.0, 160.0, 60.0, 60.0),
CGRectMake(280.0, 240.0, 60.0, 60.0),
};
CGContextFillRects(context, fillRects, 3);

最终效果图

Rect

画一个五角星

三角函数 正弦、余弦

对于任意角三角函数,x方向为正右方。

  • x = 半径 * cos(角度)
  • y = 半径 * sin(角度)

构建路径

五角星,那肯定就是有五个角,每隔一个角画路径。
占比 2/5 弧度。圆的弧度是2π
那么就是 4 * π/5。

1
2
3
4
5
6
7
8
9
CGPoint center = CGPointMake(120.0, 120.0);
CGFloat radius = 60.0;
CGContextMoveToPoint(context, center.x + radius, center.y);
for (int i = 1; i < 5; ++i) {
CGFloat x = center.x + radius * cosf(i * 4 * M_PI / 5);
CGFloat y = center.y - radius * sinf(i * 4 * M_PI / 5);
CGContextAddLineToPoint(context, x, y);
}
CGContextStrokePath(context);

pentagram

CGContextClosePath

自动连接结束点与起始点。形成一个闭环。
添加到CGContextStrokePath代码之前。

1
2
CGContextClosePath(context);
CGContextStrokePath(context);

Filling a Path

填充一个路径的时候,路径里面的子路径都是独立填充的。
假如是重叠的路径,决定一个点是否被填充,有两种规则

  1. nonzero winding number rule:非零绕数规则,假如一个点被从左到右跨过,计数器+1,从右到左跨过,计数器-1,最后,如果结果是0,那么不填充,如果是非零,那么填充。
  2. even-odd rule: 奇偶规则,假如一个点被跨过,那么+1,最后是奇数,那么要被填充,偶数则不填充,和方向没有关系。

Concentric circles filled using different fill rules

CGPathDrawingMode

决定是Stroke 还是Fill。

CGPathDrawingMode 描述
kCGPathFill 填充(用非零饶数规则)、不绘制边框
kCGPathEOFill 填充(用奇偶规则)
kCGPathStroke 边框(画线)
kCGPathFillStroke 填充 + 边框
kCGPathEOFillStroke 填充(用奇偶规则)+ 边框

kCGPathFill
PathFill

kCGPathEOFill
PathEOFill

kCGPathStroke
PathStroke

kCGPathFillStroke
PathFillStroke

kCGPathEOFillStroke
PathEOFillStroke

椭圆、圆

CGContextAddEllipseInRect

在当前路径添加一个椭圆,根据指定的Rect画椭圆。
如果CGRect的宽高相等,那就是一个圆,不相等就是一个椭圆。

1
2
3
CGContextSetLineWidth(context, 2.0);
CGContextAddEllipseInRect(context, CGRectMake(30.0, 130.0, 120, 100));
CGContextStrokePath(context);

CGContextStrokeEllipseInRect

CGContextAddEllipseInRect + CGContextStrokePath的便利方法。

1
CGContextStrokeEllipseInRect(context, CGRectMake(30.0, 260.0, 100.0, 100.0));

CGContextFillEllipseInRect

CGContextAddEllipseInRect + CGContextFillPath的便利方法。会填充

1
CGContextFillEllipseInRect(context, CGRectMake(30.0, 370.0, 100.0, 100.0));

圆弧

1
2
CGContextAddArc(context, 180.0, 150.0, 30.0, 0, M_PI_2, false);
CGContextStrokePath(context);

画两个方向相反的弧

第一跳弧线结束点与第二条弧线开始点会自动连线成一个线段。

1
2
3
CGContextAddArc(context, 210.0, 150.0, 30.0, 0, M_PI_2, false);
CGContextAddArc(context, 210.0, 150.0, 30.0, 3.0 * M_PI_2, M_PI, true);
CGContextStrokePath(context);

反向相反的弧

画两个方向相同的弧

1
2
CGContextAddArc(context, 210.0, 240.0, 30.0, 0.0, M_PI_2, false);
CGContextAddArc(context, 210.0, 240.0, 30.0, M_PI, 3.0 * M_PI_2, false);

反向相同的弧

CGContextAddArcToPoint

函数CGContextAddArcToPoint用于为矩形创建内切弧的场景。Quartz使用我们提供的端点创建两条正切线。同样我们需要提供圆的半径。弧心是两条半径的交叉点,每条半径都与相应的正切线垂直。弧的两个端点是正切线的正切点,如图。红色的部分是实际绘制的部分。
CGContextAddArcToPoint

1
2
3
4
5
6
7
8
9
10
11
12
CGPoint p[3] = {
CGPointMake(210.0, 390.0),
CGPointMake(210.0, 450.0),
CGPointMake(270.0, 450.0),
};
CGContextMoveToPoint(context, p[0].x, p[0].y);
CGContextAddArcToPoint(context, p[1].x, p[1].y, p[2].x, p[2].y, 60.0);
CGContextStrokePath(context);
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextAddLines(context, p, 3);
CGContextStrokePath(context);

CGContextAddArcToPoint

画带有圆角的矩形

画圆角矩形就是使用CGContextAddArcToPoint,画四个小圆角。
相对使用UIBezierPath而言,使用Quartz2D 有些麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CGRect rrect = CGRectMake(280, 130.0, 160.0, 160.0);
CGFloat radius = 20.0;
CGFloat minx = CGRectGetMinX(rrect);
CGFloat midx = CGRectGetMidX(rrect);
CGFloat maxx = CGRectGetMaxX(rrect);
CGFloat miny = CGRectGetMinY(rrect);
CGFloat midy = CGRectGetMidY(rrect);
CGFloat maxy = CGRectGetMaxY(rrect);
CGContextMoveToPoint(context, minx, midy);
CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);

如果想要某个角不要圆角,那么就把半径设置为0。
例如右上角不要圆角。
RoundedRectangle

1
CGContextAddArcToPoint(context, maxx, miny, maxx, midy, 0);

RoundedRectangleRight

参考

  1. Quartz 2D Programming Guide
  2. Quartz 2D Programming Guide 中文

源代码

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

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