CodeV

8.6-分列绘制

图8-25演示了Core Text中的一个基本问题。 这涉及将Core Text绘制到列中。当您布置列时,文本应该在每列的边缘换行,而不是在它们之间继续。例如,在右列的顶部,单词sitting应该出现在第一列的第二行,因为它原本是在tired of之后的。相反,它放置在右侧绘图第二列的顶部。文本穿过列,然后在下一列绘制,而不是在第一列一直向下绘制,然后在下一列再向下绘制。

图8-25

图8-25文本本身并不知道在这个复杂的路径中在哪里换行。在开始下一行之前,文本将跨两列进行流动(绘制)而不是分别在每一列向下移动。

问题是Core Text框架列表将整个Bezier路径视为一个单一的形状(您在图8-26中看到的路径)。它的两个竖直矩形意味着显示独立的文本列。 然而,发生的情况是,Core Text framesetter只使用一个测试来构建其框架。它确定(只考虑)路径内的点和路径外的点。所有其他注意事项都省略。Core Text和iOS通常没有“列”的概念,所以默认的技术不支持我们所期望的逐列布局。

图8-26

图8-26该路径包含两个矩形。

图8-27显示了我想要的布局。在这里,sittingtired of之后进行了合适的换行。 文本在第一列向下排列,然后继续到第二列再向下排列。此布局将两列视为单个的输出流。 文本从一列移动到另一列。

图8-27

图8-27本文以合适的方式遵从列布局来排列。

比较图8-25和8-27中的视觉风格。图8-27看起来是正确的“文本”布局,一系列格式良好的短段落。图8-25右列显示了更多的间隙线和较少的文本。其布局看起来略逊于图8-27中的布局。

我通过调用清单8-1中的DrawAttributedStringInBezierPath()生成了图8-25的“before”映像。从图8-25中的错误布局流到图8-27中的正确布局,它实际上只需要很少的工作来实现此功能。清单8-2显示了是实现的。

这个新函数叫做DrawAttributedStringIntoSubpath()。 它以一个一个的子路径为基础,并使用无法绘制到路径中的属性字符串内容更新字符串余数参数。要完成此操作,它会查询Core Text框架的可见字符串范围。此函数计算属性字符串的余数,即不可见部分,并将其分配给余数参数。

清单8-2中的第二个函数是DrawAttributedStringInBezierSubpaths()。此入口点遍历路径的子路径。在每个阶段,它检索“其余”字符串并将其应用到下一个绘图阶段。该函数在完成绘制子路径或其余字符串长度减少到零时返回。

清单8-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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void DrawAttributedStringIntoSubpath(
UIBezierPath *path, NSAttributedString *attributedString,
NSAttributedString **remainder)
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL)
COMPLAIN_AND_BAIL(@"No context to draw into", nil);

// Handle vertical mirroring
UIBezierPath *copy = [path safeCopy];
MirrorPathVerticallyInContext(copy);

// Establish the framesetter and retrieve the frame
CTFramesetterRef framesetter =
CTFramesetterCreateWithAttributedString(
(__bridge CFAttributedStringRef) attributedString);

CTFrameRef theFrame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(0, attributedString.length), copy.CGPath, NULL);

// If the remainder can be dereferenced, calculate
// the remaining attributed string
if (remainder)
{
CFRange range = CTFrameGetVisibleStringRange(theFrame);
NSInteger startLocation = range.location + range.length;
NSInteger extent = attributedString.length - startLocation;
NSAttributedString *substring =
[attributedString attributedSubstringFromRange:
NSMakeRange(startLocation, extent)];
*remainder = substring;
}

// Perform the drawing in Quartz coordinates
PushDraw(^{
FlipContextVertically(GetUIKitContextSize());
CTFrameDraw(theFrame, UIGraphicsGetCurrentContext());
});

// Clean up the Core Text objects
CFRelease(theFrame);
CFRelease(framesetter);
}

void DrawAttributedStringInBezierSubpaths(UIBezierPath *path,
NSAttributedString *attributedString)
{
NSAttributedString *string;
NSAttributedString *remainder = attributedString;

// Iterate through subpaths, drawing the
// attributed string into each section
for (UIBezierPath *subpath in path.subpaths)
{
string = remainder;
DrawAttributedStringIntoSubpath(
subpath, string, &remainder);
if (remainder.length == 0) return;
}
}

Image Cutouts

清单8-2最适用于绘图上下文中的列或其他独立元素。对于使用偶数/奇数填充规则创建的孔的布局不适用。如果您使用DrawAttributedStringInBezierSubpaths()函数,您将最终在整个路径上绘制文本,然后再次进入该孔,因为外部路径及其内部孔将分为两个不同的子路径。

要处理利用偶数/奇数填充规则的复杂路径,请以让局部(路径)有意义的方式执行自己的路径分解。然后直接调用DrawAttributedStringIntoSubpath()函数。

也就是说,偶数/奇数填充规则使您能够在路径中创建简单的Cutouts。这些可以应用图像绘制,如图8-28所示。为了绘制这个,我创建了一个内部矩形,并将其添加到我的路径。 这建立了一个足够大的“洞”来绘制我的图像。

图8-28

图8-28将图像绘制到路径切口(cutout)上。


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