天天品尝iOS7甜点 :: Day 21 :: Multi-column TextKit text rendering

这篇文章是天天品尝iOS7甜点系列的一部分,你可以查看完整的系列目录:天天品尝iOS7甜点


在过去,要想在iOS中创建多列的布局的文字是十分困难的:可能你可以创建多个UITextView然后手动剪裁文字来填充到不同的视图中,如果是动态内容的话就悲剧了,或者你可以调用更加底层的CoreText布局引擎,但是这个并不是那么容易使用的。

在iOS7中有关TextKit的介绍就改变了这些,并且它变得很易用,可以用来创建不同的文字排版,包括多页,多列和禁区。在今天的文章中我们将会看查查如果使用它来构建一个多列的文字展示,它渲染成一个简单的文本文件。

本章的实例程序能够在github上面进行访问,访问地址:github.com/ShinobiControls/iOS7-day-by-day

TextKit

TextKit是一个巨大的框架,在今天的文章中,我们并不会去详细的解释它全部的东西,为了能够弄懂多列的排版项目,其中有4个类我们会很熟悉的:

  • NSTextStorage: 是NSAttributedString的子类,并且包含了内容和我们希望渲染的标记的文本。它使编辑和保存相关布局管理器的引用来通知底层的文本存储。
  • NSLayoutManager: 代表管理渲染存储中的文本在一个或者多个容器对象中。把底层的unicode字符转换成图形。可以有多个文本容器允许不同地区之间流动的文本。
  • NSTextContainer: 定义当前要渲染文本的所有区域。这是提供的字形布局管理器,它指定填充该区域。可以使用UIBezierPath对象作为禁区。
  • UITextView: 真实的渲染文本到屏幕上面。在iOS7中它有所更新,添加了一个NSTextContainer的构造。

我们将会使用这些类来创建多列的文本视图。如果你要更深入的获得有关TextKit的架构信息并且如何来使用,可以查看TextKit Tutorial

Multiple Columns

我们将会把所有的代码放到一个试图控制器中,所以需要很多的实例变量来表示文本存储和布局管理器:

1
2
3
4
5
@interface SCViewController() {
NSLayoutManager *_layoutManager;
NSTextStorage *_textStorage;
}
@end

我们将会在viewDidLoad中具体的创建他们,首先,我们需要先看一下文本存储,我们在资源文件中有一个.txt的文件,它包含了一些文本信息.由于NSTextStorageNSAttributeString的子类,所以我们可以运用这个方法initWithFileURL:options:documentAttributes:error进行构造:

1
2
3
// Import the content into a text storage object
NSURL *contentURL = [[NSBundle mainBundle] URLForResource:@"content" withExtension:@"txt"];
_textStorage = [[NSTextStorage alloc] initWithFileURL:contentURL options:nil documentAttributes:NULL error:NULL];

创建一个简单的布局管理器:

1
2
3
4
5
6
// Create a layout manager
_layoutManager = [[NSLayoutManager alloc] init];
[_textStorage addLayoutManager:_layoutManager];

// Layout the text containers
[self layoutTextContainers];

一旦我们创建了_layoutManager,我们把它添加到_textStorage中。它不仅仅是提供了文本内容的布局管理器,还如果底层的文本改变了布局控制器将会得到相应的通知。

viewDidLoad方法的最后我们调用了layoutTextContainers方法,它是一个工具类方法,我们现在来具体介绍。

我们来循环每个列,创建新的NSTextContainer来指定文本的大小,然后使用屏幕上面的UITextView进行渲染它。循环体如下所示:

1
2
3
4
5
6
7
8
9
NSUInteger lastRenderedGlyph = 0;
CGFLoat currentXOffset = 0;
while (lastRenderedGlyph < _layoutManager.numberOfGlyphs) {
...
}

// Need to update the scrollView size
CGSize contentSize = CGSizeMake(currentXOffset, CGRectGetHeight(self.scrollView.bounds));
self.scrollView.contentSize = contentSize;

我们设置了一些变量——其中一个代表了循环终止(lastRenderdGlyph),还有一个存储了当前列的X轴偏移。NSLayoutManager有一个属性,它包含了所有字形的总数,所以我们需要循环知道我们已经绘制出来为止。

当循环完成之后,我们需要计算出当前内容的的正确大小,然后对scrollView进行设置,所以我们才能够像预期一样移动页数。

在循环体中,首先,我们需要计算出当前列的大小:

1
2
CGRect textViewFrame = CGRectMake(currentXOffset, 10, CGRectGetWidth(self.view.bounds)/2, CGRectGetHeight(self.view.bounds) - 20);
CGSize columnSize = CGSizeMake(CGRectGetWidth(textViewFrame) - 20, CGRectGetHeight(textViewFrame) - 10);

我们设置列为接近根视图的高,宽为根视图的一半。

现在我们可以创建一个NSTextContainer来对列区域中的字形进行布局。

1
2
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:columnSize];
[_layoutManager addTextContainer:textContainer];

同事我们还需要把文本容器添加到布局管理器中。这就确保了容器提供有序列的字形来渲染展示。

为了获得容器来呈现在屏幕上面,我们需要创建一个UITextView:

1
2
3
4
// And a text view to render it
UITextView *textView = [[UITextView alloc] initWithFrame:textViewFrame textContainer:textContainer];
textView.scrollEnabled = NO;
[self.scrollView addSubview:textView];

在这里我们指定了text view的容器。

最后我们需要更新追踪最后的呈现字形的变量和当前列的位置:

1
2
3
4
5
// Increse the current offset
currentXOffset += CGRectGetWidth(textViewFrame);

// And find the index of the glyph we've just rendered
lastRenderedGlyph = NSMaxRange([_layoutManager glyphRangeForTextContainer:textContainer]);

到这里就完成了,可能你还会有所疑惑,是的,它趋势完成了,如果你运行应用程序就会看到下面的呈现效果。

Conclusion - 总结

在iOS7中,TextKit是一个主要的附加功能,并且它代表了一些非常强大的功能。我今天只是很简单的介绍了如果简单的使用.我鼓励您进一步调查TextKit如果你显示任何多少量的文本——实际上,这是一个新领域的iOS7,用在制作不错的文档。

本文翻译自:iOS7 Day-by-Day :: Day 21 :: Multi-column TextKit text rendering

文章目录
  1. 1. TextKit
  2. 2. Multiple Columns
  3. 3. Conclusion - 总结
,