“站在巨人的肩上
Standing on Shoulders of Giants.”

Core Text

文档版本

  • 2015-02-16 - 创建文档

本文结构

摘要:本文讲述Core Text基础概念,然后实现一个显示文本的样例程序,并指出代码中容易出现问题地方。

** 关键字:iOS8 Xcode6 Core Text 入门教程 排版概念 仿射**

背景

我们有一个不上架的阅读项目,同事用UIWebView展示HTML,最近我想使用Core Text重新实现。之前没接触Core Text,只能边学边用。学习过程中参考了不少资料,多数资料写于2013年,相同的代码在Xcode 6.1.1、iOS 8.1中无法得出原作者一致的效果。几经折腾,现在分享学习成果,故有了本系列博客。

编程环境:OS X Yosemite 10.10.2,Xcode 6.1.1
运行环境:iOS 8.1.3

Core Text的框架位置

Core Text的框架位置

从图片可知,Core Text位于UIKit和Core Graphics/Quartz之间:

  • 使用UIKit可简单拖拽将文本显示在UILabel上,但无法单独控制文本中每个字的颜色。GitHub有个代替UILabel的项目:TTTAttributedLabel,可支持链接植入等功能。
  • 使用Core Graphics/Quartz,可做到系统能够做到的一切,但需要计算文本每个字符绘制在屏幕上的坐标。
  • Core Text恰巧位于二者之间,可完全控制文字的位置、布局以及颜色大小等属性,同时Core Text简化了一些工作——例如从文本换行到字体的渲染。

排版基础

Core Text主要用来对文本进行排版布局和字体处理,故需了解字符(Character)、字形(Glyph)和字体(Font)等概念。

  • 字体(Character):一个符号,数字或者文字等。
  • 字形(Glyph):对于同一套字符有不同的写法,形态或样式。字形是字符的一族形态,例如Helvetica,New Roman。
  • 字体(Font):在字形的基础上进行的加工修饰便构成了字体,如加粗,倾斜,加颜色,改变磅数等。例如
    9pt Helvetica Bold是一个字体。

Core Text基础数据类型

Core Text的编码流程完全按照Text Layout数据流进行,因此熟悉Text Layout数据流非常重要。

Text Layout数据流

  • CFAttributedString:属性字符串或称特性字符串,即是格式化的字符串,可包含字体、前景色、下划线、段落等排版信息。
  • CTFramesetter(CTFramesetterRef):通过CFAttributedString进行初始化,它作为CTFrame对象的生产工厂,负责根据path生产对应的CTFrame。
  • CTTypesetter:自动生成,无需处理。
  • CGPath:用于指示绘制区域
  • CTFrame(CTFrameRef):通过CTFrameDraw函数直接绘制到context上,可在绘制前,操作CTFrame中的CTLine进行微调。
  • CTLine(CTLineRef)为Core Text绘制中的一行的对象,通过它可以获得当前行的line ascent,line descent,line leading,还可以获得Line下的所有Glyph Runs。
  • CTRun或称Glyph Run,是一组共享相同特性(attributes)的字形集合

有关字体、段落信息,后续博客再作讲解,一次全交代略显复杂,分步走平缓学习曲线。

例子:显示普通文本

现在,根据Text Layout数据流编写一个简单的程序。此程序通过重载UIView的 -drawRect: 方法在屏幕上显示一串不带格式的文本,带格式及段落样式和图文混排的做法会在后续博客讲解。先从基础开始。

1 创建一个Single View Application应用。
2 由于Core Text需要使用CGContext,故添加一个UIView的子类,命名为SimpleCoreTextView。
3 在Storyboard中指定默认UIViewController的UIView的Class为SimpleCoreTextView。
4 重载SimpleCoreTextView的 -drawRect: 方法,加入如下代码,外加引入头文件 #import <CoreText/CoreText.h>

// 1 带属性的字符串,由此开始Core Text后续操作
NSAttributedString *contentString = [[NSAttributedString alloc] initWithString:@"你好,Core Text!"];
// 2 由1创建CTFramesetter,在Core Text中用CTFramesetterRef类型表示
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge void*)contentString);
// 3 创建用于Core Text绘制的图形上下文可变路径并添加整个屏幕范围作为其绘制区域,由CGMutablePathRef类型表示
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
// 4 获取图形上下文,由CGContextRef类型表示
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 5 添加路径到图形上下文中
CGContextAddPath(ctx, path);
// 6 由CTFramesetter创建CTFrame,由CTFrameRef类型表示
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, contentString.length), path, NULL);
// 7 设置每个字形(Glyph)不参与当前变换矩阵(Current Transformation Matrix)
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
// 8 将Core Text绘图原点从左下角上升至左上角,与UIView一致,即是把y轴平移一个屏幕高度,因为当前绘制的区域为整个屏幕。多余的40点使文字超出屏幕顶部40点,在下一步倒转y轴朝向后将在(0,40)位置显示文本内容。
CGContextTranslateCTM(ctx, 0, self.bounds.size.height + 40);
// 9 沿x轴翻转y轴,使之向下增长,与UIView坐标轴朝向一致,1.0表示不变,-1.0表示反方向
CGContextScaleCTM(ctx, 1.0, -1.0);
// 10 绘制内容
CTFrameDraw(frame, ctx);
// 11 释放资源
CFRelease(framesetter);
CFRelease(frame);
CFRelease(path);

如果不添加7~9行,则绘制了的文本将出现在屏幕左下角,这是由于Core Text坐标系与UIView的坐标系不同所致,故需转换。

由于使用Core Foundation的数据结构,所有后缀为Ref的数据类型,若通过Create或Copy结尾的函数获得,则必须在使用结束时进行释放,否则造成内存泄露。

运行结果
运行结果
示例代码托管在GitHub

参考

  1. CoreText学习(一)Base Objects of Core Text
  2. how-to-create-a-simple-magazine-app-with-core-text
  3. Core Text入门
posted @ 2015-02-16 16:42  米高 | Michael  阅读(1389)  评论(0编辑  收藏  举报