CodeV

4.3-构建路径

当系统提供的路径(如矩形和椭圆形)不足以满足您的需要时,您可以迭代地构建路径。可以通过逐点布置项目,随时添加曲线和直线来创建路径。

每个贝塞尔路径可以包括各种几何元素,包括以下:

  • moveToPoint:建立的定位请求。
  • addLineToPoint:添加直线。
  • addCurveToPoint:controlPoint1:controlPoint2:创建的三次贝塞尔曲线段。
  • addQuadCurveToPoint:controlPoint:创建的二次贝塞尔曲线段。
  • 通过调用addArcToCenter:radius:startAngle:endAngle:clockwise:添加的弧。

图4-4显示了由一系列三次贝塞尔曲线组成的星形。例4-2详细描述了图形后面的代码。它建立一条新路径,告诉它移动到起点(p0),然后添加一系列三次曲线来构建星形。

图4-4

图4-4这种形状在Photoshop中开始。Pixel Cut’s PaintCode将其转换为示例4-2中的代码。

如果这个代码看起来特别不清晰明朗,好吧,是的。构造贝塞尔曲线不是一个代码友好的过程。事实上,这个特定的路径开始是在Photoshop里面出现。 我绘制了一个形状,并将其保存到PSD文件。然后我使用PaintCode(从Mac App Store获得$ 99 + $ 20 购买应用程序中的Photoshop文件导入功能)将矢量艺术图形转换为一系列Objective-C调用。

当涉及到绘图时,最适合表达形状的工具通常位于Xcode外部。设计这些形状后,你可以通过代码来管理和开发你的应用程序的材料。因此,您不必担心在Photoshop或其他工具中建立的确切偏移量,位置和大小。正如你会发现的,UIBezierPath实例表达矢量艺术。他们可以缩放,平移,旋转等等,所有控制取决于你的命令,所以你可以完全按照你的应用程序的需要来调整材料。

示例4-2 创建星形路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Create new path, courtesy of PaintCode (paintcodeapp.com)
UIBezierPath* bezierPath = [UIBezierPath bezierPath];

// Move to the start of the path, at the right side
[bezierPath moveToPoint: CGPointMake(883.23, 430.54)]; // p0

// Add the cubic segments
[bezierPath addCurveToPoint: CGPointMake(749.25, 358.4) // p1
controlPoint1: CGPointMake(873.68, 370.91)
controlPoint2: CGPointMake(809.43, 367.95)];
[bezierPath addCurveToPoint: CGPointMake(668.1, 353.25) // p2
controlPoint1: CGPointMake(721.92, 354.07)
controlPoint2: CGPointMake(690.4, 362.15)];
[bezierPath addCurveToPoint: CGPointMake(492.9, 156.15) // p3
controlPoint1: CGPointMake(575.39, 316.25)
controlPoint2: CGPointMake(629.21, 155.47)];
[bezierPath addCurveToPoint: CGPointMake(461.98, 169.03) //p4
controlPoint1: CGPointMake(482.59, 160.45)
controlPoint2: CGPointMake(472.29, 164.74)];
[bezierPath addCurveToPoint: CGPointMake(365.36, 345.52) // p5
controlPoint1: CGPointMake(409.88, 207.98)
controlPoint2: CGPointMake(415.22, 305.32)];
[bezierPath addCurveToPoint: CGPointMake(262.31, 358.4) //p6
controlPoint1: CGPointMake(341.9, 364.44)
controlPoint2: CGPointMake(300.41, 352.37)];
[bezierPath addCurveToPoint: CGPointMake(133.48, 460.17) // p7
controlPoint1: CGPointMake(200.89, 368.12)
controlPoint2: CGPointMake(118.62, 376.61)];
[bezierPath addCurveToPoint: CGPointMake(277.77, 622.49) // p8
controlPoint1: CGPointMake(148.46, 544.36)
controlPoint2: CGPointMake(258.55, 560.05)];
[bezierPath addCurveToPoint: CGPointMake(277.77, 871.12) // p9
controlPoint1: CGPointMake(301.89, 700.9)
controlPoint2: CGPointMake(193.24, 819.76)];
[bezierPath addCurveToPoint: CGPointMake(513.51, 798.97) // p10
controlPoint1: CGPointMake(382.76, 934.9)
controlPoint2: CGPointMake(435.24, 786.06)];
[bezierPath addCurveToPoint: CGPointMake(723.49, 878.84) // p11
controlPoint1: CGPointMake(582.42, 810.35)
controlPoint2: CGPointMake(628.93, 907.89)];
[bezierPath addCurveToPoint: CGPointMake(740.24, 628.93) // p12
controlPoint1: CGPointMake(834.7, 844.69)
controlPoint2: CGPointMake(722.44, 699.2)];
[bezierPath addCurveToPoint: CGPointMake(883.23, 430.54) // p0
controlPoint1: CGPointMake(756.58, 564.39)
controlPoint2: CGPointMake(899.19, 530.23)];

绘制贝塞尔路径

创建Bezier路径实例后,可以通过应用填充(fill)或(stroke)描边将它们绘制到上下文中。填充(Filling)绘制一条路径,为路径中的所有区域添加颜色。描边(Stroking)勾勒出一条路径,仅绘制边缘,使用存储在路径的lineWidth属性中的线宽。典型的绘图模式可能如下所示:

1
2
3
4
5
myPath.lineWidth = 4.0f;
[[UIColor blackColor] setStroke];
[[UIColor redColor] setFill];
[myPath fill];
[myPath stroke];

此代码段为路径分配线宽,设置当前上下文的填充和描边颜色,然后填充和描边路径。

清单4-1介绍了我认为的一个更方便的方法。方法定义一个类类别,添加两种新的绘制方式。这个类别将绘图序列归纳如下:

1
2
[myPath fill:[UIColor redColor]];
[myPath stroke:4 color:[UIColor blackColor]];

颜色和描边宽度成为单个绘图请求的参数。这些函数使您能够创建一系列绘图操作,而无需在请求之间更新上下文或实例属性。

所有绘图发生在第1章中介绍的图形状态(GState)保存和恢复请求中。这确保传递的参数不会持续超过方法调用,因此您可以返回到完全相同的上下文和路径状态。

Note

当使用生产代码(即,不是在写一本书,而是试图提供易于阅读的示例),请确保为您的类别增加命名空间。添加自定义前缀,以确保您的扩展不会与任何可能的未来Apple类更新重叠。esStroke:color:不像stroke:color:那么漂亮,但它可为您的代码提供长期保护。

清单4-1 绘图实用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@implementation UIBezierPath (HandyUtilities)
// Draw with width
- (void) stroke: (CGFloat) width color: (UIColor *) color
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL)
{
NSLog(@"Error: No context to draw to");
return;
}

CGContextSaveGState(context);

// Set the color
if (color) [color setStroke];

// Store the width
CGFloat holdWidth = self.lineWidth;
self.lineWidth = width;

// Draw
[self stroke];

// Restore the width
self.lineWidth = holdWidth;
CGContextRestoreGState(context);
}

// Fill with supplied color
- (void) fill: (UIColor *) fillColor
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL)
{
NSLog(@"Error: No context to draw to");
return;
}
CGContextSaveGState(context);
[fillColor set];
[self fill];
CGContextRestoreGState(context);
}

###Drawing Inside Paths
在UIKit中,UIRectFrame()函数是在作为参数提供的矩形内绘制一行。此函数提供特别干净和吸引人的结果。这是清单4-2的灵感。

清单4-2执行与图4-3中所示相同的描边操作,但有两处轻微的改动。首先,它使描边的尺寸加倍。第二,它使用addClip剪切图形。

通常,描边操作在路径边缘的中心绘制描边。这将创建一个有一半在边缘的一边另一半在边缘的另一边的描边。将大小加倍确保了描边的内半部分使用了您指定的大小。

正如你在第1章中发现的,剪切创建一个蒙层。它排除了被添加到剪切边界外的上下文中的材料。在清单4-2中,剪切防止描边继续经过路径的外边缘,因此所有绘图都会在路径内部出现,结果是完全绘制在路径中的固定大小的描边。

清单 4-2 Stroke Inside a Path

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void) strokeInside: (CGFloat) width color: (UIColor *) color
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL)
{
NSLog(@"Error: No context to draw to");
return;
}

CGContextSaveGState(context);
[self addClip];
[self stroke:width * 2 color:color]; // Listing 4-1
CGContextRestoreGState(context);
}

填充路径和奇/偶填充规则

在矢量图形中,绕组规则(winding rules)确定区域是在路径内部还是外部。这些规则影响在填充路径时区域是否被着色,如图4-5所示,Quartz使用偶/奇规则,它是一种确定任何区域在路径“内部”的程度的算法。

图4-5

图4-5奇偶填充规则确定Quartz是填充整个内部路径还是仅填充“内部”区域。左侧的形状使用默认填充规则。右侧的形状应用奇偶填充规则。

在许多绘图场景中使用此规则。例如,奇偶填充规则允许您绘制复杂边框。它提供了一种从文本布局中剪切部分区域的方法,以便可以插入图像。它提供了反转选择的基础,使您能够创建正和负的空间等等。

该算法通过将光线(具有一个固定端,指向给定方向的线)从路径内的点投影到其外部的远点来测试遏制(containment)。该算法计算光线穿过任何线的次数。如果光线通过偶数个交点,则该点在形状外; 如果通过奇数个交点,则该点在形状内。

在图4-5左侧的嵌套方形示例中,中心的一个点在通过路径之前通过四个后续行。在第三和第四内部正方形之间的刚好越过该盒子的点在出口处将仅穿过三条线。因此,根据偶数/奇数填充规则,第一点是形状的“外部”,因为它通过偶数线。第二点是形状的“内部”,因为它通过一个奇数。

这些偶数和奇数具有重大意义,您可以在图4-5中看到。当您启用路径的usesEvenOddFillRule属性时,UIKit使用此计算来确定位于形状内部的哪些区域应填充,以及外部和不应该包含哪些区域。

Quartz的上下文填充函数的特殊版本CGContextEOFillPath()通过应用奇偶填充规则来执行上下文填充。当您使用正常版本的函数CGContextFillPath()时,不应用此规则。


本文翻译自《iOS Drawing Practical UIKit Solutions》作者:Erica Sadun,翻译:Cheng Dong。如果觉得本书不错请购买支持正版:亚马逊购买传送门,本书所有源代码可在GitHub上下载。译者虽然力求做到信,达,雅,但是由于时间仓促加之译者水平十分有限,文中难免会出现不正确,不准确,词不达意,难于理解的地方,还望各位批评指正,共同进步,谢谢。转载请注明出处。