ios开发text kit_第9章 iOS 7中文字排版和渲染引擎——Text Kit

本文详细介绍了iOS 7中引入的Text Kit技术,它是用于文字排版和渲染的解决方案。Text Kit不是替代Core Text,而是提供了更易于使用的接口,特别是对于UILabel、UITextField和UITextView等文本控件。文章讲解了Text Kit的基础、架构,包括NSTextContainer、NSLayoutManager和NSTextStorage的核心类,以及如何通过Text Kit实现凸版印刷效果。此外,还探讨了文字图片混合排版和动态字体设置,使开发者能够更好地适应用户偏好。
摘要由CSDN通过智能技术生成

第 9 章 iOS 7中文字排版和渲染引擎——Text Kit

在iOS 7之前,应用中字体的大小用户是不能设置的,而且开发人员要想实现多种样式的文字排版是件非常麻烦的事情。在iOS 7之后,这些问题都解决了,Text Kit就是解决这些问题的钥匙。本章将向大家介绍iOS 7中文字排版和渲染引擎——Text Kit。

9.1 Text Kit基础

Text Kit最主要的作用就是为程序提供文字排版和渲染的功能。通过Text Kit可以对文字进行存储、布局,以更加精准的排版方式来显示文本内容。Text Kit隶属于UIKit框架,其中包含了一些文字排版的相关类和协议。

9.1.1 文字的排版和渲染

在iOS 7之前也有一种用于文字排版和渲染的技术——Core Text,而引入Text Kit的目的并非要取代Core Text。Core Text是面向底层的文字排版和渲染技术,如果我们需要将文本内容直接渲染到图形上下文时,从性能角度考虑,最佳方案就是使用Core Text。但是从易用性角度考虑,使用Text Kit是最好的选择,因为它能够直接使用UIKit提供的一些文本控件,例如:UITextView、UILabel和UITextField,对文字进行排版。

Text Kit具有很多优点:文本控件UITextView、UITextField和UILabel是构建于Text Kit之上的。Text Kit完全掌控着文字的排版和渲染:可以调整字距、行距、文字大小,指定特定的字体,对文字进行分页或分栏,支持富文本编辑、自定义文字截断,支持文字的换行、折叠和着色等处理,支持凸版印刷效果。

9.1.2 Text Kit架构

在开始介绍Text Kit API之前,我们有必要理解一下iOS的文字渲染框架。从图9-1可见,Text Kit是基于Core Text构建的,它通过Core Text与Core Graphics进行交互。而文本控件,如:UILabel、UITextField和UITextView,则构建于Text Kit之上,可见这些文本控件可以利用Text Kit提供的API来对文字进行排版和渲染处理。从图9-1可见,我们也可以看到UIWebView是基于WebKit的,它不能使用Text Kit提供的功能。

图9-1 iOS 7之后的文字渲染

图9-2所示是iOS 7之前的文字渲染,可以看出在iOS 7之前没有Text Kit。文本控件,如:UILabel、UITextField和UITextView是基于String Drawing和WebKit构建的。其中String Drawing与Core Graphics直接通信。因此在iOS 7之前文本控件也可以实现多种样式的文字排版,但是事实上是通过WebKit实现的。WebKit是一种浏览器内核技术,使用它进行文字渲染会消耗掉比较多的内存,对应用的性能有一定的影响。

图9-2 iOS 7之前的文字渲染

9.1.3 Text Kit中的核心类

我们在使用Text Kit时,会涉及如下核心类。

NSTextContainer。定义了文本可以排版的区域。默认情况下是矩形区域,如果是其他形状的区域,需要通过子类化NSTextContainer来创建。

NSLayoutManager。该类负责对文字进行编辑排版处理,将存储在NSTextStorage中的数据转换为可以在视图控件中显示的文本内容,并把字符编码映射到对应的字形上,然后将字形排版到NSTextContainer定义的区域中。

NSTextStorage。主要用来存储文本的字符和相关属性,是NSMutableAttributedString的子类(见图9-3)。此外,当NSTextStorage中的字符或属性发生改变时,会通知NSLayoutManager,进而做到文本内容的显示更新。

NSAttributedString。支持渲染不同风格的文本。

NSMutableAttributedString。可变类型的NSAttributedString,是NSAttributedString的子类(见图9-3)。

图9-3 NSAttributedString类图

NSLayoutManager、NSTextContainer、NSTextStorage之间究竟是什么关系呢?图9-4所示文本控件通过它们实现了显示文本内容到屏幕上的过程。NSLayoutManager对象从NSTextStorage对象中取得文本内容,进行排版,然后把排版之后的文本放到NSTextContainer对象指定的区域上。最后再由一个文本控件从NSTextContainer中取出内容显示到屏幕中。

图9-4 NSLayoutManager、NSTextContainer和NSTextStorage之间的关系

NSLayoutManager对象起到承上启下的作用。还记得铅字排版吗?在没有计算机排版的时代,排版工人都是通过这种方法实现的,他们从铅字库中找到特定字体的字母,然后把它放到活动字模中(见图9-5),最后进行印刷。这个过程可以很好地帮助我们理解NSLayoutManager、NSTextContainer和NSTextStorage之间的关系,其中NSLayoutManager对象相当于排版工人,NSTextStorage对象相当于特定字体的铅字库,而NSTextContainer对象就相当于我们看到的活动字模。文本控件从NSTextContainer中取出内容显示到屏幕的过程就相当于印刷的过程。

图9-5 铅字排版1

9.1.4 实例:凸版印刷效果

为了更好地理解我们前面介绍的API内容,下面我们通过一个实例介绍NSLayoutManager、NSTextContainer和NSTextStorage三者之间的关系。

在Xcode中选择Single View Application模板,创建一个名为TextKit_Sample的工程,在创建时选择Devices为Universal。工程创建成功后,打开Main_iPhone.storyboard故事板文件,从对象库中拖曳TextView控件到设计视图上,并修改其文本内容,如图9-6所示。

图9-6 拖曳TextView控件

拖曳完成后,要为其定义输出口属性。ViewController.h文件代码如下:

#import

@interface ViewController : UIViewController

@property (nonatomic,strong) NSTextContainer* textContainer; ①

@property (strong, nonatomic) IBOutlet UITextView *textView; ②

- (void) markWord:(NSString*)word inTextStorage:(NSTextStorage*)textStorage; ③

@end

上述代码第①行声明了NSTextContainer类型的属性textContainer。代码第②行声明了TextView控件属性。第③行代码声明一个方法,用于设置某些单词样式风格。

ViewController.m文件中viewDidLoad方法代码如下:

- (void)viewDidLoad

{

[super viewDidLoad];

CGRect textViewRect = CGRectInset(self.view.bounds, 10.0, 20.0); ①

NSTextStorage* textStorage = [[NSTextStorage alloc] initWithString:_textView.text];②

NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init]; ③

[textStorage addLayoutManager:layoutManager]; ④

_textContainer = [[NSTextContainer alloc] initWithSize:textViewRect.size]; ⑤

[layoutManager addTextContainer:_textContainer]; ⑥

[_textView removeFromSuperview]; ⑦

_textView = [[UITextView alloc] initWithFrame:textViewRect

textContainer:_textContainer]; ⑧

[self.view addSubview:_textView]; ⑨

//设置凸版印刷效果

[textStorage beginEditing]; ⑩

NSDictionary *attrsDic = @{NSTextEffectAttributeName: NSTextEffectLetterpressStyle};

NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc]

initWithString:_textView.text attributes:attrsDic];

[textStorage setAttributedString:attrStr];

[self markWord:@"我" inTextStorage:textStorage];

[self markWord:@"I" inTextStorage:textStorage];

[textStorage endEditing];

}

上述代码第①行是创建一个矩形区域,这个区域是通过CGRectInset函数创建的,这个函数能够指定一个中心点,后面的两个参数沿着self.view.bounds区域向内缩进量。这样可以使得文字部分不会太靠近视图的边界。

第②行代码是创建NSTextStorage对象,它需要一个字符串作为构造方法的参数,这里我们是从TextView控件取出来赋值给它的。第③行代码是创建NSLayoutManager对象。第④行代码是将刚刚创建的NSTextStorage和NSLayoutManager对象关联起来。第⑤行代码是创建NSTextContainer对象,在创建它的时候,通过构造方法设置它的区域,我们这里设置的区域与TextView区域是相同的。第⑥行代码是将刚刚创建的NSLayoutManager和NSTextContainer对象关联起来。

第⑦~⑨行代码是重新构建原来的TextView控件,并且重新添加到视图上。这主要是因为只有重新创建代码才能通过Text Kit中NSLayoutManager来管理,而原来在Interface Builder中创建的TextView控件不再使用了。

第⑩~⑯行代码是实现设置凸版印刷效果,这些设置代码是需要放在[textStorage beginEditing]和[textStorage endEditing]之间的。第⑪行代码是声明一个字典对象,其中包括@{NSTextEffectAttribute

Name:NSTextEffectLetterpressStyle},NSTextEffectAttributeName是文本效果键,而NSTextEffectLetterpressStyle是文本效果值,这里面它们都是常量。第⑫行代码是创建NSMutableAttributedString对象,在构造方法中需要指定要设置的文本以及文本的样式。第⑬行代码是将通过NSTextStorage对象的setAttributedString:方法设置文本NSTextStorage还有类似的方法addAttributedString:,该方法是添加新的设置文本,这样可以叠加多种效果。第⑭~⑮行代码是调用markWord:inTextStorage:方法实现特定单词的查找,并添加设置效果。

ViewController.m文件中的markWord:inTextStorage:方法代码如下:

- (void) markWord:(NSString*)word inTextStorage:(NSTextStorage*)textStorage

{

NSRegularExpression *regex = [NSRegularExpression

regularExpressionWithPattern:word

options:0 error:nil]; ①

NSArray *matches = [regex matchesInString:_textView.text

options:0

range:NSMakeRange(0, [_textView.text length])]; ②

for (NSTextCheckingResult *match in matches) { ③

NSRange matchRange = [match range];

[textStorage addAttribute:NSForegroundColorAttributeName

value:[UIColor redColor]

range:matchRange]; ④

}

}

上述代码第①行是创建正则表达式NSRegularExpression对象,其中的regularExpressionWithPattern参数指定正则表达式。第②行代码通过正则表达式NSRegularExpression对象对TextView中的文本内容进行扫描,结果放到数组中。第③行代码从集合中取出NSTextCheckingResult结果对象。第④行代码是为找到的文本设置颜色为红色风格。

编码完成之后我们就可以运行一下看看效果了,如图9-7所示,其中的“我”和“I”是红色显示的,整个的文字设置为凸版印刷效果。

图9-7 运行效果

9.2 文字图片混合排版

读者喜欢阅读图文并茂的文章,因此在应用界面中,有时不仅仅要有文字,还要有图片,这就涉及文字和图片的混排了。在图文混排过程中必然会涉及文字环绕图片的情况,很多文字处理软件(如Word、WPS、Open Office等)都有这种功能。Text Kit通过环绕路径(exclusion paths)将文字按照指定的路径环绕在图片等视图对象的

周围(见图9-8)。

图9-8 环绕路径

下面我们看看如何通过环绕路径实现文字图片混合排版。我们可以在上一节的案例基础上修改,打开Main_iPhone.storyboard故事板文件,从对象库中拖曳ImageView控件到设计视图上,如图9-9所示,通过设置Image属性设置要显示的图片为MetalType.png,当然我们之前需要将图片导入到工程中。

图9-9 拖曳ImageView到设计视图

我们看看具体代码,ViewController.m文件主要代码如下:

- (void)viewDidLoad

{

[super viewDidLoad];

CGRect textViewRect = CGRectInset(self.view.bounds, 10.0, 20.0);

NSTextStorage* textStorage = [[NSTextStorage alloc] initWithString:_textView.text];

NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init];

[textStorage addLayoutManager:layoutManager];

_textContainer = [[NSTextContainer alloc] initWithSize:textViewRect.size];

[layoutManager addTextContainer:_textContainer];

[_textView removeFromSuperview];

_textView = [[UITextView alloc] initWithFrame:textViewRect

textContainer:_textContainer];

[self.view insertSubview:_textView belowSubview:_imageView]; ①

//设置凸版印刷效果

[textStorage beginEditing];

NSDictionary *attrsDic = @{NSTextEffectAttributeName: NSTextEffectLetterpressStyle};

NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc]

initWithString:_textView.text attributes:attrsDic];

[textStorage setAttributedString:attrStr];

[textStorage endEditing];

_textView.textContainer.exclusionPaths = @[[self translatedBezierPath]]; ②

}

- (UIBezierPath *)translatedBezierPath ③

{

CGRect imageRect = [self.textView convertRect:_imageView.frame fromView:self.view]; ④

UIBezierPath *newPath = [UIBezierPath bezierPathWithRect:imageRect]; ⑤

return newPath;

}

上述代码第①行是重新将TextView控件添加到View上,但是又必须考虑到不遮挡ImageView,因此需要使用View的insertSubview:belowSubview:方法添加,belowSubview指定ImageView,这样新添加的TextView就在ImageView之下了。第②行代码是使用TextView的_textView.textContainer.exclusionPath属性指定环绕路径,该属性是NSArray类型,也就是说可以设定多个环绕路径,而且在NSArray数组中元素是一种UIBezierPath类型。

注意 UIBezierPath类可以创建基于贝塞尔曲线2路径,此类是Core Graphics框架关于图形绘制路径的一个封装,使用此类可以定义简单的形状,如椭圆、矩形,或者由多个直线和曲线段组成的形状。

2贝赛尔(Bézier)曲线是法国数学家贝塞尔在工作中发现,任何一条曲线都可以通过与它相切的控制线两端的点的位置来定义。因此,贝赛尔曲线可以用4个点描述,其中两个点描述两个端点,另外两个描述每一端的切线。贝赛尔曲线可以分为:二次方贝赛尔曲线和高阶贝赛尔曲线。

获得ImageView的贝塞尔曲线路径是通过第③行代码的translatedBezierPath方法实现的。代码中的第④行是进行坐标系转换,如图9-10所示,原来ImageView的坐标系是相对于View的坐标(62, 72),通过convertRect: fromView:方法将坐标系转换为相对于TextView的坐标(52, 52)。这是因为我们需要获得的是TextView的文字围绕ImageView路径,因此坐标系需要参照TextView。

图9-10 坐标系的转换

编码完成之后我们就可以运行一下看看效果了。

9.3 动态字体

以前的iOS用户会抱怨,为什么不能设置自定义字体呢?在iOS 7系统之后苹果对于字体在显示上做了一些优化,让不同大小的字体在屏幕上都能清晰地显示。通常用户设置了自己偏好的字体了,用户可以在图9-11所示的步骤(设置→通用→辅助功能)设置粗体文字的过程。用户还可以在图9-12所示的步骤(设置→通用→文字大小)是设置文字大小的过程。

图9-11 设置粗体文本

图9-12 设置文字大小

但是并不是在设置中进行设置就万事大吉了,我们还要在应用代码中进行编程,以应对这些变化。我们需要在应用中给文本控件设置为用户设置的字体,而不是在代码中硬编码字体及大小。iOS 7中可以通过UIFont中新增的preferredFontForTextStyle:方法来获取用户设置的字体。

iOS 7中提供了6种字体样式供选择。

UIFontTextStyleHeadline。标题字体,例如:报纸的标题。

UIFontTextStyleSubheadline。子标题字体。

UIFontTextStyleBody。正文字体。

UIFontTextStyleFootnote。脚注字体。

UIFontTextStyleCaption1。标题字体,一般用于照片或者字幕。

UIFontTextStyleCaption2。另一个可选Caption字体。

这6种字体具体样式可见图9-13所示。

图9-13 iOS系统提供的6种字体样式

处理系统提供了6种样式的字体,我们还可以自己定义字体。

当用户在设置中改变了字体,系统会给应用程序发送UIContentSizeCategoryDidChangeNotification通知,我们需要监听这个通知,并通过下面的代码重新设置文本控件字体即可。

self.textView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];

为了能够更好地理解动态字体,下面我们通过一个实例介绍一下。我们对9.1.3节的案例修改一下,我们看看具体代码,ViewController.m文件主要代码如下:

- (void)viewDidLoad

{

[super viewDidLoad];

CGRect textViewRect = CGRectInset(self.view.bounds, 10.0, 20.0);

NSTextStorage* textStorage = [[NSTextStorage alloc] initWithString:_textView.text];

NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init];

[textStorage addLayoutManager:layoutManager];

_textContainer = [[NSTextContainer alloc] initWithSize:textViewRect.size];

[layoutManager addTextContainer:_textContainer];

[_textView removeFromSuperview];

_textView = [[UITextView alloc] initWithFrame:textViewRect

textContainer:_textContainer];

[self.view addSubview:_textView];

//设置凸版印刷效果

[textStorage beginEditing];

NSDictionary *attrsDic = @{NSTextEffectAttributeName: NSTextEffectLetterpressStyle};

NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:

_textView.text attributes:attrsDic];

[textStorage setAttributedString:attrStr];

[self markWord:@"我" inTextStorage:textStorage];

[self markWord:@"I" inTextStorage:textStorage];

[textStorage endEditing];

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(preferredContentSizeChanged:)

name:UIContentSizeCategoryDidChangeNotification

object:nil]; ①

}

- (void)preferredContentSizeChanged:(NSNotification *)notification{ ②

self.textView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; ③

}

在上述代码第①行是注册监听UIContentSizeCategoryDidChangeNotification通知,当系统发出这个通知后,会回调preferredContentSizeChanged:方法。代码第②行所定义的就是preferredContentSizeChanged:回调方法。在这个方法中我们通过第③行代码实现重新设置TextView的字体样式。

编码完成之后我们就可以运行一下看看效果了,如图9-14所示是运行之后通过系统设置改变文字大小前后的对比。

图9-14 改变文字大小前后

在这个案例基础上大家可以改变不同的字体风格看看运行的效果。

9.4 小结

在本章中,我们首先介绍了iOS 7的Text Kit技术,通过Text Kit技术我们实现了文本图片混合排版,动态字体设置等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值