CoreText简介
Core Text 是基于 iOS 3.2+ 和 OSX 10.5+ 的一种能够对文本格式和文本布局进行精细控制的文本引擎。
它良好的结合了 UIKit 和 Core Graphics/Quartz:
>
Core Graphics/Quartz几乎允许你做任何系统允许的事情,但你需要为每个字形计算位置,并画在屏幕上。
Core Text正结合了这两者!你可以完全控制位置、布局、类似文本大小和颜色这样的属性,而Core Text将帮你完善其它的东西——类似文本换行、字体呈现等等。
iOS7新推出的类库Textkit,其实是在之前推出的CoreText上的封装
CoreText的主要作用也是用于文字的排版和渲染,但它是一种先进而又处于底层技术,如果我们需要将文本内容直接渲染到图形上下文(Graphics context)时,从性能和易用性来考虑,最佳方案就是使用CoreText。
### 富文本
>
富文本格式(RTF)规范是为了便于在应用程序之间轻松转储格式化文本和图形的一种编码方法。
现在,用户可以利用特定转换软件,在不同系统如MS-DOS、Windows、OS/2、Macintosh和Power Macintosh的应用程序之间转移字处理文档。
RTF规范提供一种在不同的输出设备、操作环境和操作系统之间交换文本和图形的一种格式。
RTF使用ANSI,PC-8, Macintosh, 或IBM PC字符集控制文档的表示法和格式化,包括屏幕显示和打印。
凭借RTF规范,不同的操作系统和不同的软件程序创建的文档能够在这些操作系统和应用程序之间传递。
将一个格式化的文件转换为RTF文件的软件称为RTF书写器。
RTF书写器用于分离现有文本中的程序控制信息,并且生成一个包含文本和与之相关的RTF组的新文件。
将RTF文件转换成格式化文件的软件则称为RTF阅读器。
简单来说附带有每一个文字属性的字符串,就是富文本。在iOS中,
AttributeString
专门用来处理富文本。AttributedString
也分为NSAttributedString
和NSMutableAttributedString
两个类。常用的一些方法
|
|
代码示例
|
|
### CoreText坐标系与UIKit坐标系

从图中可看出CoreText坐标系是以左下角为坐标原点,而我们常用的UIKit是以左上角为坐标原点。因此在CoreText中的布局完成后需要对其坐标系进行转换,否则直接绘制出现位置反转的镜像情况。
>
在iOS的不同framework中使用着不同的坐标系:
UIKit - y轴向下
Core Graphics(Quartz) - y轴向上
OpenGL ES- y轴向上
UIKit是iPhone SDK的Cocoa Touch层的核心framework,是iPhone应用程序图形界面和事件驱动的基础,它和传统的windows桌面一样,坐标系是y轴向下的;Core Graphics(Quartz)一个基于2D的图形绘制引擎,它的坐标系则是y轴向上的;而OpenGL ES是iPhone SDK的2D和3D绘制引擎,它使用左手坐标系,它的坐标系也是y轴向上的,如果不考虑z轴,在二维下它的坐标系和Quartz是一样的。
### CoreText绘制富文本
CoreText实现图文混排其实就是在富文本中插入一个空白的图片占位符的富文本字符串,通过代理设置相关的图片尺寸信息,根据从富文本得到的frame计算图片绘制的frame再绘制图片这么一个过程。
#### 整体代码
|
|
所有的绘制操作都是在上下文上进行绘制的
|
|
coreText 起初是为OSX设计的,而OSX得坐标原点是左下角,y轴正方向朝上。iOS中坐标原点是左上角,y轴正方向向下。若不进行坐标转换,则文字从下开始,还是倒着的。这三句对context的坐标系进行转换
>context说的是绘画人所处的角度上下文,画布无论怎么样都是正对着屏幕的,它不会旋转,或者放大缩小,或者移动,认为context就是画布这种理解是错误的
CTM,Context Translate Matrix。 它是把要绘制的上下文以一个叫做Matrix的东西来表示,可以简单地想作,绘制的上下文的每一个点都映射在Matrix上,你在Matrix上的操作都会使得上下文上的点产生相应的变动。如放大、旋转、移动。
|
|
coreText中大量的调用c的方法。大部分跟系统底层有关的都需要调c的方法。所以设置代理要按照人家的方法来啊。
补充一下知识
这是一个CTRun的尺寸图,我们绘制图片的时候实际上实在一个CTRun中绘制这个图片,那么CTRun绘制的坐标系中,它会以origin点作为原点进行绘制。基线为过原点的x轴,ascent即为CTRun顶线距基线的距离,descent即为底线距基线的距离。
我们绘制图片应该从原点开始绘制,图片的高度及宽度及CTRun的高度及宽度,我们通过代理设置CTRun的尺寸间接设置图片的尺寸。
|
|
上面只是设置了回调结构体,然而我们还没有告诉这个代理我们要的图片尺寸。
所以这句话就在设置代理的时候绑定了一个返回图片尺寸的字典。
事实上此处你可以绑定任意对象
。此处你绑定的对象既是回调方法中的参数ref
。
三个回调方法代码如下
|
|
由于是c的方法,所以也没有什么对象的概念。是一个指针类型的数据。不过oc的对象其实也就是c的结构体。我们可以通过类型转换获得oc中的字典。__bridge既是C的结构体转换成OC对象时需要的一个修饰词。
图片的插入(创建一个富文本类型的图片占位符,绑定我们的代理)
|
|
C中就是传递指针的数据比如说字符串,数组时转换不需要用__bridge
需要手动释放是因为进行了类型转换之后就不属于对象了,也不再归自动引用计数机制管理了,所以手动管理。
|
|
绘制文本
|
|
frameSetter
是根据富文本生成的一个frame
生成的工厂,你可以通过framesetter
以及你想要绘制的富文本的范围获取该CTRun的frame。
但是你需要注意的是,获取的frame是仅绘制你所需要的那部分富文本的frame。即当前情况下,你绘制范围定为(10,1),那么你得到的尺寸是只绘制(10,1)的尺寸,他应该从屏幕左上角开始(因为你改变了坐标系),而不是当你绘制全部富文本时他该在的位置。
然后建立一会绘制的尺寸,实际上就是在指定你的绘制范围。
接着生成整个富文本绘制所需要的frame
。因为范围是全部文本,所以获取的frame即为全部文本的frame(一定要搞清楚全部与指定范围获取的frame他们都是从左上角开始的,否则你会进入一个奇怪的误区,稍后会提到的)。
最后,根据你获得的frame,绘制全部富文本
CTFrame可以理解为一个整体的画布由很多行(CTLine)组成,而每一行又由一个或者多个小方块(CTRun)组成,我们不需要自己创建CTRun,Core Text将根据NSAttributedString的属性来自动创建CTRun。每个CTRun对象对应不同的属性,正因此,你可以自由的控制字体、颜色、字间距等等信息。
CTFramesetter其实就是CTFrame的工厂方法,通过给定的NSAttributedString,生成CTRrame,同时系统自动的创建了CTTypesetter,CTTypesetter就是管理你的字体的类。
绘制图片
绘制图片用下面这个方法
|
|
有三个参数,分别是context,frame,以及image。context就是当前的上下文image就是要添加的那个图片,不过是CGImage类型,通过UIImage转出CGImage就好了,下面讲一下frame的获取:-(CGRect)calculateImageRectWithFrame:(CTFrameRef)frame
。
|
|
因为CTFrameGetLines()返回值是CFArrayRef类型的数据。就是一个c的数组类型吧
每个CTLine都有自己的origin。所以要生成一个相同元素个数的数组去盛放origin对象。
然后用CTFrameGetLineOrigins获取所有原点。
计算frame
遍历frame中的所有CTRun,检查是不是我们绑定图片的那个,如果是,根据该CTRun所在CTLine的origin以及CTRun在CTLine中的横向偏移量计算出CTRun的原点,加上其尺寸即为该CTRun的尺寸。
|
|
手动释放创建的对象
|
|
两坐标系中point和frame的变换
point
UIKit坐标系上的point转换为CoreText坐标系上的point,在UIKit坐标系上的一个点为(x,y),其在CoreText坐标系上为(x,self.bounds.size.height - y)
反过来转换也是一样的在CoreText坐标系上的一个点为(x,y),其在UIKit坐标系上为(x,self.bounds.size.height - y)frame
UIKit坐标系上的frame转换为CoreText坐标系上的frame,在UIKit坐标系上的一个frame为(x,y,width,height),其在CoreText坐标系上为(x,self.bounds.size.height - y - height,width,height)
反过来转换也是一样的在CoreText坐标系上的一个frame为(x,y,width,height),其在UIKit坐标系上为(x,self.bounds.size.height - y - height,width,height)
实现点击事件
通过touchBegan方法拿到当前点击到的点,然后通过坐标判断这个点是否在某段文字上,如果在则触发对应事件。
整体代码
|
|
分段解析
|
|
坐标转换
因为UIKit坐标系与系统坐标系的不同,我们要将坐标系统一为CoreText坐标
|
|
点击图片判断
|
|
|
|
由于传入的point是CoreText坐标(本例中),所以frame我们一定要传入CoreText坐标系下的frame才能正确对应。
点击文字判断
|
|
|
|
|
|
获取一行文字中,指定charIndex字符相对x原点的偏移量,返回值与第三个参数同为一个值。如果charIndex超出一行的字符长度则反回最大长度结束位置的偏移量,如一行文字共有17个字符,哪么返回的是第18个字符的起始偏移,即第17个偏移+第17个字符占有的宽度=第18个起始位置的偏移。因此想求一行字符所占的像素长度时,就可以使用此函数,将charIndex设置为大于字符长度即可。
|
|
CoreText实现图文混排之文字环绕及点击算法
整体代码
|
|
为什么cirP的rect是CGRectMake(100, 100, 100, 200),这个排除的区域却在那里?原因就在于UIKit坐标系统跟CoreText坐标系统的区别。
绘制椭圆图片可以借助github上的一个工具库DWImageUtils 贴上代码
|
|
点击事件获取
1、主流方式:CTLineGetStringIndexForPosition
主流方式就是当前大部分基于CoreText封装的富文本展示类(包括TTTAttributedLabel、NIAttributedLabel和FTCoreTextView)中使用的方法 CTLineGetStringIndexForPosition。这个方法是获取当前点在所在文字处于当前绘制文本的索引值。事实上如果没有一些其他因素的话,能使用这个方法是最简便快捷的
但是在实际使用中CTLineGetStringIndexForPosition这个方法获取一个字的index范围是这个字前面大概半个字开始到这个字中间的位置,从这个字的中间到这个字的后半个字就会获得下一个字的index。
CTLineGetStringIndexForPosition这个方法还有另一个作用还是很好用的。这个方法最好的用处就是判断一行CTLine最多容纳多少的字符,只需把这个point的x位置调很大(超过CTFrame path的宽度)就可以了。
2、遍历CTRun比较法 (代码中的写法)
一次遍历中拿到所有活动图片和活动文字的frame,按照点击图片的处理方式处理文字,在添加点击事件的活动文本的特征点中添加加了click(属性名随便写,不要太low就行了)这么一个属性。通过遍历CTRun将活动文本的frame算出来,并存到一个数组里面arrText。
[arrText addObject:[NSValue valueWithCGRect:[self getLocWithFrame:frame CTLine:line CTRun:run origin:point]]];
注意:文字frame不同于获取图片的frame。由于图片是在一个空白占位符上绘制文字,所以一定是以一个CTRun进行绘制的。但是第一篇文章中老司机说过,每个CTRun是所有具有相同属性的连续同行文字的集合。针对CTRun的特性,我们不难想到,文字由于可能出现两行,也有可能会活动文本的字体字号等其他属性不尽相同导致一段文字由两个CTRun进行绘制,所以不能单纯的保存一个frame,而是要以一个数组容纳他。再通过一些逻辑将不同的活动文本区别开来。
参考:
CoreText实现图文混排
CoreText实现图文混排之点击事件
CoreText实现图文混排之文字环绕及点击算法
CoreText中坐标转换的一些理解
CTLineRef详细介绍
CoreText原理及基本使用方法
CoreText 实现图文混排
使用CoreText实现图文混排