iOS:基于CoreText的排版引擎

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介:

一、CoreText的简介

CoreText是用于处理文字和字体的底层技术。它直接和Core Graphics(又被称为Quartz)打交道。Quartz是一个2D图形渲染引擎,能够处理OSX和iOS中图形显示问题。Quartz能够直接处理字体(font)和字形(glyphs),将文字渲染到界面上,它是基础库中唯一能够处理字形的模块。因此CoreText为了排版,需要将显示的文字内容、位置、字体、字形直接传递给Quartz。与其他UI组件相比,由于CoreText直接和Quartz来交互,所以它具有更高效的排版功能。

下面是CoreText的架构图,可以看到,CoreText处在非常底层的位置,上层的UI控件(包含UILable、UITextField及UITextView)和UIWebView都是基于CoreText来实现的。

 

UIWebview也是处理复杂的文字排版的备选方案。对于排版,基于CoreText和基于UIWebView相比,具有以下不同点:

  • CoreText占用内存更少,渲染速度更快,UIWebView占用内存多,渲染速度慢。
  • CoreText在渲染界面前就可以精确地获得显示内容的高度(只要有了CTFrame即可),而UIWebView只有渲染出内容后,才能获得内容的高度(而且还需要通过JavaScript代码来获取)。
  • CoreText的CTFrame可以在后台线程渲染,UIWebView的内容只能在主线程(UI线程)渲染。
  • 基于CoreText可以做更好的原生交互效果,交互效果可以更细腻。而UIWebView的交互效果都是利用JavaScript来实现的,在交互效果上会有一些卡顿情况存在。例如,在UIWebView下,一个简单的按钮按下操作,都无法做出原生按钮的即时和细腻的按下效果。

当然,基于CoreText的排版方案也有那么一些劣势:

  • CoreText渲染出来的内容不能像UIWebView那样方便的支付内容的复制。
  • 基于CoreText来排版需要自己处理很多复杂逻辑,例如需要自己处理图片和文字混排相关的逻辑,也需要自己实现链接点击操作的支持。
复制代码
1、图文混排
CTFrameRef  textFrame     // coreText 的 frame
CTLineRef   line          // coreText 的 line
CTRunRef    run           // line  中的部分文字

2、相关方法:
CFArrayRef CTFrameGetLines(CTFrameRef frame) //获取包含CTLineRef的数组
void CTFrameGetLineOrigins(CTFrameRef frame,CFRange range,CGPoint origins[])//获取所有CTLineRef的原点
CFRange CTLineGetStringRange(CTLineRef line) //获取line中文字在整段文字中的Range
CFArrayRef CTLineGetGlyphRuns(CTLineRef line)//获取line中包含所有run的数组
CFRange CTRunGetStringRange(CTRunRef run)//获取run在整段文字中的Range
CFIndex CTLineGetStringIndexForPosition(CTLineRef line,CGPoint position)//获取点击处position文字在整段文字中的index
CGFloat CTLineGetOffsetForStringIndex(CTLineRef line,CFIndex charIndex,CGFloat* secondaryOffset)//获取整段文字中charIndex位置的字符相对line的原点的x值
复制代码

 

二、基于CoreText的基础排版引擎

简单实现步骤:

a.自定义View,重写drawRect方法,后面的操作均在其中进行

b.得到当前绘图上下问文,用于后续将内容绘制在画布上

c.将坐标系翻转

d.创建绘制的区域,写入要绘制的内容

示例1:不带图片的排版引擎,只是显示文本内容,而且不设置文字的属性信息

自定义的CTDispalyView.m

  View Code

在ViewController.m实现显示

  View Code

演示结果截图

 

 

三、基于CoreText的基本封装

发现,虽然上面效果确实达到了我们的要求,但是,很有局限性,因为它仅仅是展示了CoreText排版的基本功能而已。要制作一个比较完善的排版引擎,我们不能简单的将所有的代码都放到CTDisplayView的drawRect方法中。根据设计模式的“单一功能原则”,我们应该把功能拆分,把不同的功能都放到各自不同的类里面进行。

对于一个复杂的排版引擎来说,可以将功能拆分为以下几个类来完成:

1、一个显示用的类,仅仅负责显示内容,不负责排版

2、一个模型类,用于承载显示所需要的所有数据

3、一个排版类,用于实现文字内容的排版

4、一个配置类,用于实现一些排版时的可配置项

例如定义的4个类分别为:

CTFrameParserConfig类:用于配置绘制的参数,例如文字颜色、大小、行间距等

CTFrameParser类:用于生成最后绘制界面需要的CTFrameRef实例

CoreTextData类:用于保存由CTFrameParser类生成的CTFrameRef实例,以及CTFrameRef实际绘制需要的高度

CTDisplayView类:持有CoreTextData类实例,负责将CFFrameRef绘制在界面上。

关于这4个类的关键代码如下:

CTFrameParserConfig

  View Code
  View Code

CTFrameParser

  View Code
  View Code

CoreTextData

  View Code
  View Code

CTDisplayView

  View Code
  View Code

除了这4个类外,在代码中还创建了基本的宏定义和分类Category,分别是CoreTextDemo.pch、UIView+Frame.h(快速访问view的尺寸)

CoreTextDemo.pch

  View Code

UIView+Frame.h

  View Code
  View Code

示例2:不带图片的排版引擎,只是显示文本内容,设置文字的一些简单的属性信息

  View Code

演示结果截图

 

好了,效果确实是实现了,现在来看看本框架的UML示意图,这4个类的关系是这样的:

1、CTFrameParser通过CTFrameParserConfig实例来生成CoreTextData实例;

2、CTDisplayView通过持有CoreTextData实例来获取绘制所需要的所有信息;

3、ViewController类通过配置CTFrameParserConfig实例,进而获得生成的CoreTextData实例,最后将其赋值给CTDisplayView成员,达到将指定内容显示在界面的效果。

 

四、定制排版文件格式

对于上面的例子,我们给CTFrameParser增加了一个将NSString转换为CoreTextData的方法。但是这样的实现方式有很多的局限性,因为整个内容虽然可以定制字体大小、颜色、行高等信息,但是却不能支持定制内容中某一个部分。例如,如果我们只想让内容的某几个字显示成红色并将字体变大,而让其他的文字显示成黑色而且字体不变,那么就办不到了。

解决办法:让CTFrameParser支持接受NSAttributeString作为参数,然后在ViewController中设置我们想要的NSAttributeString信息。

更改后的CTFrameParser

  View Code
  View Code

示例3:不带图片的排版引擎,只是显示文本内容,通过富文本更改文字的一些简单的属性信息

  View Code

演示结果截图

 

更进一步,实际工作中,我们更希望通过一个排版文件,来设置需要排版的文字的内容、颜色、字体大小等信息。我们规定排版的模板文件为JSON格式。排版格式示例文件如下:

  View Code

通过苹果提供的NSJSONSeriallization类,我们可以将上面的模板文件转换成NSArray数组,每一个数组元素是一个Dictionary,代表一段相同设置的文字。为了简单,我们配置文件只支持配置颜色和字号,但是以后可以根据同样的思想,很方便地增加其他配置信息。

现在修改CTFrameParser类,增加如下的这些方法,让其可以从如上格式的模板文件中生成CoreTextData。最终实现代码如下:

更改后的CTFrameParser:

  View Code
  View Code

示例4:不带图片的排版引擎,只是显示文本内容,通过排版文件格式更改文字的一些简单的属性信息

  View Code

演示结果截图

 

可以看到,通过一个简单的模板文件,我们可以很方便地定义排版的配置信息了。

 

五、支持图文混排的排版引擎

在上面的示例中,我们在设置模板文件的时候,就专门在模板文件里面预留了一个名为type的字段,用于表示内容的类型。之前的type的值都是txt,这次,我们增加一个img的值,用于表示图片。同时给img类型的内容还需要配置3个属性如下:

1、width:用于设置图片显示的宽度

2、height:用于设置图片显示的高度

3、name:用于设置图片的资源名

也即文件格式如下:

 

在改造代码之前,先来了解一下CTFrame内部的CTLine和CTRun。

在CTFrame内部,是有多个CTLine类组成的,每一个CTLine代表一行,每个CTLine又是由多个CTRun来组成,每一个CTRun代表一组显示风格一致的文本。我们不用手工管理CTLine和CTRun的创建过程。

CTLine和CTRun示意图如下:

示意图解释:

可以看到,第一行的CTLine是由两个CTRun构成的,第一个CTRun为红色大字号的左边部分,第二个CTRun为右边黑色小字号部分。

虽然我们不用管理CTRun的创建过程,但是我们可以设置某一个具体的CTRun的CTRunDelegate来指定该文本在绘制时的高度、宽度、排列对齐方式等信息。

对于图片的排版,其实,CoreText本质上是不支持的,但是,可以在显示文本的地方,用一个特殊的空白字符代替,同时设置该字体的CTRunDelegate信息为要显示的图片的宽度和高度信息,这样最后生成的CTFrame实例,就会在绘制时将图片的位置预留出来。以后,在CTDisplayView的drawRect方法中使CGContextDrawImage方法直接绘制出来就行了。

改造模板解析类,要做的工作有:

  • 增加一个CoreTextImageData类,寄存图片信息
  • 改造CTFrameParser的parserTemplateFile:(NSString *)path config:(CTFrameParserConfig *)config方法,使其支持type为omg的节点解析。并且对type为omg的节点,设置其CTRunDelegate信息,使其在绘制时,为图片预留相应的空白位置。
  • 改造CoreTextData类,增加图片相关的信息,并且增加计算图片绘制局域的逻辑。
  • 改造CTDisplayView类,增加绘制图片的相关的逻辑。

具体的改造如下:

新添加CoreTextImageData类:

  View Code
  View Code

修改CTFrameParser解析类:

  View Code
  View Code

改造CoreTextData类:

  View Code
  View Code

改造CTDisplayView类:

  View Code
  View Code

示例5:带图片的排版引擎,显示文本内容和图片,通过排版文件格式更改文字的一些简单的属性信息

  View Code

测试效果图如下:

 

六、添加对图片的点击支持

实现方式

为了实现对图片的点击支持,我们需要给CTDisplayView类增加用户点击操作的检测函数,在检测函数中,判断当前用户点击的局域是否在图片上,如果在图片上,则触发点击图片的逻辑。拼过提供的UITapGestureRecognizer可以很好地满足我们的要求,所以我们这里用它来检测用户的点击操作。

这里我们实现的是点击图片后,显示图片。实际开发中,可以根据业务需求去调整点击后的效果。

CTDisplayView类实现如下,增加点击手势:

  View Code

点击图片演示截图:

 

 

七、添加对链接的点击支持

实现方式:需要修改模板文件,增加一个名为”link”的类型,用于表示链接内容。格式如下:

首先增加一个CoreTextLinkData类,用于记录解析JSON文件时的链接信息:

CoreTextLinkData

  View Code
  View Code

接着增加一个工具类CoreTextUtils类,用于检测链接是否被点击:

CoreTextUtils:

  View Code
  View Code

然后依次改造CTFrameParser类,CoreTextData类,CTDisplayView类

CTFrameParser:

  View Code
  View Code

CoreTextData:

  View Code
  View Code

CTDisplayView

  View Code
  View Code

 

测试截图:

 

源码链接https://github.com/xiayuanquan/CoreTextKit.git

本博文摘自唐巧《iOS开发进阶》,本人花了点时间学习并做了一下整理和改动,希望对学习这方面知识的人有帮助。

 

程序猿神奇的手,每时每刻,这双手都在改变着世界的交互方式!
本文转自当天真遇到现实博客园博客,原文链接:http://www.cnblogs.com/XYQ-208910/p/6222931.html ,如需转载请自行联系原作者
相关文章
|
Web App开发 前端开发 JavaScript
React Native 0.64版本发布,iOS开启支持Hermes引擎
React Native 0.64版本发布,iOS开启支持Hermes引擎
549 0
|
iOS开发 API
iOS文字排版(CoreText)
<p><span style="color:rgb(51,51,51); font-family:'Helvetica Neue',Helvetica,STheiti,微软雅黑,黑体,Arial,Tahoma,sans-serif,serif; font-size:14px; line-height:25px; background-color:rgb(240,240,240)">和我们平
1675 0
|
10天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。