Core Text 是基于 iOS 3.2+ 和 OSX 10.5+ 的一种能够对文本格式和文本布局进行精细控制的文本引擎。 它良好的结合了 UIKit 和 Core Graphics/Quartz:
Core Text 对于创建杂志和书籍应用十分方便——它们在 iPad 上非常受欢迎! | |
这篇教程将会引领你使用 Core Text,通过创建一个简单的杂志应用——为僵尸! 你将学会如何:
要阅读此教程,你需要先了解 iOS 开发的基础。如果你对 iOS 开发很陌生,你应该先看看这个网站上的一些其它的教程。 事不宜迟,让我们为僵尸的快乐生活做出自己应有的贡献吧——通过创建他们的专属 iPad 杂志! |
创建一个 Core Text 项目开启 Xcode,点击 File\New\New Project,选择 iOS\Application\View-based Application,并点击 Next,将项目命名为 CoreTextMagazine,选择 iPad 作为设备,点击 Next,选择保存项目的目录,点击 Create。 下一步就是为项目添加 Core Text 框架:
你已经设置完了——下面是添加代码时间! |
添加一个 Core Text 视图要尽快上手 Core Text,你需要创建一个自定义的 UIView,使用 Core Text 作为其 drawRect: 方法。 点击File\New\New File,选择 iOS\Cocoa Touch\Objective-C class,并点击 Next。输入 UIView 作为 Subclass,点击 Next,将新类命名为 CTView,并点击 Save。 在 CTView.h 文件中,在 @interface 前添加下面的代码,引用 Core Text 框架:
下一步,你将设置这个新的自定义视图为应用的主视图。 |
|
打开 CTView.m 删除所有预定义的方法。 输入下面的代码在你的视图上绘制一个“Hello world”:
注意,当使用 Core Text 类时您使用的是 CTFramesetterCreateWithAttributedString 与 CTFramesetterCreateFrame 这样的函数,而是直接使用 Objective-C 对象。 |
你可能会想“既然已经又了 Objective-C,为什么我还要用 C ?!” 好吧,为了简捷,iOS 的很多底层库都是用 plain C 编写的。不用担心,Core Text 的函数应用起来很简单。 只有一件事要牢记:在你引用名字中有 “Create” 的函数时,不要忘记使用 CFRelease。 不管你信不信,这就是用 Core Text 画简单文本的所有东西!点击运行,看看结果。
噢,看上去有点不对劲,是吧?跟很多底层 API 一样,Core Text 使用 Y翻转坐标系统。更糟糕的是,内容的呈现也是上下翻转的。注意,当你混合使用 UIKit 绘图和 Core Text 绘图时,你将获得很奇葩的结果。 |
然后修改内容的方向!在 “CGContextRef context = UIGraphicsGetCurrentContext();” 一行后添加代码如下:
代码很简单,只是通过转换内容将其翻转。你只需要在画 CT 时复制/粘帖它们。 再运行一次——恭喜你完成了第一个 Core Text 应用!
|
Core Text 对象模型如果您对 CTFramesetter 与 CTFrame 还有些不明白。这里我来做一个有关 Core Text 如何渲染文本内容的简述。 Core Text 对象模型如下:
您创建 CTFramesetter 关联您提供的 NSAttributedString 。此时 CTTypesetter 实例将自动创建, 它管理您的字体。下一步使用 CTFramesetter 创建您要用于渲染文本的一个或多个帧。 当您创建帧时,您指定一个用于此帧矩形内的子文本范围。Core Text 为每行文本自动创建一个 CTLine (注意这里) 与并创建多个 CTRun 文本分段,每个 CTRun 内的文本有着同样的格式。 例如,Core Text 可能为您的几个红色单词创建一个 CTRun,其它 CTRun 包括纯文本,另外一些 CTRun 是粗体等。再次重申,你不要自己直接创建 CTRun 实例, Core Text 使用其于您提供的 NSAttributedString 相关属性创建它们。 每个 CTRun 对象可以采用不同的属性,所以你可以精确的控制字距,连字,宽度,高度等更多属性。 |
映射到杂志应用要创建这个杂志应用,我们要具备可以将一些文本标记成具有不同属性的性能。我们可以直接使用NSAttributedString的方法来做到这点,比如setAttributes:range,但在实践中这是一种笨拙的处理方式(除非你费力地编写大量代码)。 因此,为了更简单地处理问题,我们将创建一个简单的文本标记解析器,它允许我们在杂志内容中使用简单的标签设置格式。 进到“File\New'New File“下,选择”iOS\Cocoa Touch\Objective-C class", 然后点击下一步,进入NSObject子类,点击下一步,将新类命名为MarkupParser.m再保存。 |
切换到 MarkupParser.h 文件删除所有内容并粘贴下面的代码 – 它定义了用于解析的一些属性与方法:
编写一个解析器通常是很困难的工作, 在这里我将向你展示使用正则表达示创建一个非常简单的解析器。 本教程中的解析器将非常简单,只支持开放型标签 - 一个标签设置后面文本的样式,直到出现一个新的标签,这种标签化文本看起来就像这样: These are red and blue words. 对于本教程的目的,这样的标签就足够了。对于您的项目,如果需要,你可以进一步完善。 |
开始解析!在 attrStringFromMarkup: 方法中添加:
为什么我们要创建这样的正则表达式?我们将用它来搜索的字符串相匹配的每一部分 1)绘制找到的文本块,2)按找到的标签更改当前样式。重复这个过程直到文本结束。 |
非常简单的解析器不是吗?现在您有全部文本和格式化标签分块的 "chunks" 数组, 你需要使用它的文字与标签循环创造属性化文本。 在方法体里添加:
|
注意: 如果您对这个段落中所使用的正则表达式还不是太明白,它们基本上可以称为 “查找任何 color=" 打头的文本”。 匹配所有一般字符(不包括引号),直到找到关闭引号。更多详情,查看苹果的
NSRegularExpression class reference.
很好!已经完成了渲染格式化文本的一半工作了 - 现在 attrStringFromMarkup: 可以把标记化的内容解析放置到 NSAttributedString 中为 Core Text 使用它做好了准备。 那么让我们先试试! 打开 CTView.m and 在 @implementation: 之前添加:
|
就是这样 - 点击 Run 试试看!
真是太棒了! 感谢 50 行的解析代码让我们没有在字符范围与格式上处理大量的代码任何,我们的杂志应用只需要使用一个简单的文本文件保存其内容。同时您刚刚完成的这个简单的解析器可以根据您的杂志应用的需要无限制的扩展。 |
一个基本的杂志布局到现在为止,我们已经能够把文字显示出来了,这是一个好的开端。但是对一个杂志来说,我们最好有多栏显示-在这里Core Text要大展身手了。 开始编写布局代码之前,我们先加载一个更长的字符串到应用中,这样我们就有足够长的文章需要回绕多行显示。 打开菜单栏 File\New\New File, 选择 iOS\Other\Empty, 然后点击下一步(Next)。命名新的文件为test.txt, 然后点击保存。 下来把这个文件中的文本拷贝到test.txt并保存。 打开 CTView.m 并找到我们创建MarkupParser 和NSAttributedString 的那两行,然后删除他们。我们把加载文本文件的代码从drawRect: 方法中移出来,因为他们实际上不应该在那里。drawRect: 方法的真正工作是画UIView里的内容-而不是加载内容。我们等会将会把attString变量重构成实例变量,变成这个类的属性。 |
接下来打开CoreTextMagazineViewController.m, 删除所有存在的内容,添加下面的代码:
在CTView.h中定义3个实例变量:
现在我们点击"Run"来看看view是否显示了文本文件的内容。酷!
| |||||||
如何给这些文本创建列(columns)? 很幸运,Core Text 提供了一个很方便的函数 - CTFrameGetVisibleStringRange。这个函数告诉你在指定的矩形框里可以显示多少文本。所以想法就是-创建列,看看多少文本可以显示下,如果有更多文本没显示,再创建新的列,如此循环,知道所有文本都可以显示完。 (这里列是个CTFrame实例,因为列只是更高一点的矩形) 首先我们创建列,然后页,然后整个杂志。所以...让我们使CTView继承UIScrollView,这样就继承了分页和滚动的功能了,而不用自己写! 打开 CTView.h ,修改@interface这样代码为:
好,我们得到免费的滚动和分页功能了。我们下面会轻松的开启分页功能。 到现在为止,我们在创建了drawRect:中创建了framesetter和frame实例。有多个栏而且有不同的格式,最好的我们在一次把所有的计算做完。所以我们准备创建新的类 "CTColumnView" ,这个类只是呈现(render)传给他的CT内容,在我们的CTView类中我们准备一次创建所有的CTColumnView的实例,并把他们作为subviews加入到CTView中。 |
总结一下:CTView会处理滚动,分页,创建所有的列;CTColumnView实际呈现内容到屏幕上。 打开菜单栏 File\New\New File, 选择 iOS\Cocoa Touch\Objective-C class, 点击下一步。在"Subclass of"输入框中输入, 点击下一步,把新类命名为 CTColumnView.m, 然后点击保存。下面就是CTColumnView类的初始代码:
|
让我们先添加一个数组属性来保存我们的CTView的CTframes,然后声明 buildFrames 方法,这个方法会创建所有列:
|
现在,让我在所有的CT设置完成后,调用buildFrames。在CoreTextMagazineViewController.m中的 viewDidLoad的结尾添加下面的代码
在我们试运行新代码前还需要做一件事:在 CTView.m 中删除drawRect:。我们现在在CTColumnView类中做所有的显示,所以要保留CTView的drawRect: 方法为标准的 UIScrollView 实现。 好的...点击运行(Run)然后你可以看到文本以列的方式排列了!左右拖拽页面看一下...太棒了!
我们有列,很棒排版的文字,但是没有图片。在Core Text显示图片不是那么容易-毕竟这个是文本框架阿。 但是,由于我们已经有个了小的标记(markup)解析器,我们很快速的可以添加文本中显示图片的功能! |
使用 Core Text 绘图一般来说,Core Text 并没有绘制图像的能力。然而,因为它是一个布局引擎,它所能做的是保留一个空间让你在其中绘制图像。同时,因为你的代码中已经有了 drawRect: 方法,绘制一个图像很容易。 让我们看看在文本中保留一个空间是如何工作的: 还记得所有的文本块实际上是 CTRun 的实例吗?你只需为所给的 CTRun 设置委托,委托对象会负责将 CTRun 的上升空间、下降空间和宽度告知 Core Text。如下图:
当 Core Text 获知一个拥有 CTRunDelegate 委托的 CTRun 时,它会询问委托对象 —— 我需要为这些块数据保留多少宽度和高度?这样你就在文本中建造了一个洞,然后你把图像在那里绘制出来。 |
让我们从为词法分析器添加对 “img” 标签的支持开始!打开 MarkupParser.m 并找到 “} //end of font parsing”;在此行之后紧接着添加支持“img”标签的代码:
让我们看看新代码——实际解析“img”标签同解析 font 标签不尽相同。通过3个正则表达式,你有效的获取了 img 标签的 width、height 和 src 属性。当这些完成后——你在 self.images 上添加了一个新的 NSDictionary 对象用以保存刚刚解析出来的信息,在文本中添加图片。 |
现在我们来看看第一部分 —— CTRunDelegateCallbacks 是一个保存指向函数的引用的 C 语言结构体,这个结构体提供了你想要传递给 CTRunDelegate 的信息。正如你已经猜到的那样,getWidth 方法提供一个宽度参数给 CTRun,getAscent 方法提供高度参数给 CTRun,等等。在上面的代码中你为那些处理提供了函数名称,马上我们也会添加上函数具体实现。 第二部分非常重要 —— imgAttr 字典保存了图像的维度信息,我们向这个对象发送了 retain 消息因为它将会被传递给函数处理 —— 因此,当 getAscent 触发时,它将作为参数被获得并从中读取出图像的高度并将其提供给 CTRun。干净利落是吧?(马上我们就会谈谈这个。) |
第三部分中通过关联与绑定回调和数据使用 CTRunDelegateCreate 创建委托实例。 下一步你需要创建一个属性字典 (和之前字体格式相同的方式),但在格式化属性中放入委托实例。 最后你向属性化文本里加入一个包括委托属性的空格用于之后使用图片绘制。 下一步,你可能已经预料到了,提供用于委托回调的函数:
|
现在您的解析器能处理 “img” 标签了,那么让 CTView 能渲染它们。 我们需要一个方法将图片数组发给这个视图, 让我们将设置属性化文本与图片合成到一个方法中。 添加以下代码:
转到 CoreTextMagazineViewController.m 找到 "[(CTView*)self.view setAttString: attString];" 行,使用下面的代码替换它:
|
要呈现图片,你需要明确的知道图片将显示在应用中的哪个框架。要找到这个原点,我们需要一系列的值:
现在开始呈现图片!首先我们需要更新 CTColumnView 类:
|
现在,计算图像的位置,并将其附加到相应的文本列:
我知道刚看到这些代码感觉非常低级,但和我一起忍受一下,我们已经到了本教程结尾,这是最后的冲刺! 一段段来说:
|
夜狼
|
很棒!就快好了!- 还有很小的一步:找到 CTView.m 中的 "[content setCTFrame:(id)frame];" 行添加如下代码:
没关系,我已经准备下一期的僵尸月刊 - 每月的流行僵尸杂志 - 你需要做的只是将下面内容导入:
然后切换到 CoreTextMagazineViewController.m 找到使用文件路径的地方切换为使用新的 zombies.txt 文件:
|
夜狼
|
最后一步。假如我们想合理的分配列中的文本,使它们匹配列的宽度。添加下列代码:
这样你就可以控制段落式样了;查阅苹果的 Core Text 文档中的 kCTParagraphStyleSpecifierAlignment,你将获得你可以控制的所有段落式样的列表。
|