<h1>简介</h1>
<p> UIKit框架中用来展示文本的类有UITextView、UITextField、UILabel,其中UITextView主要用来展示大量文本的,其底层是用一个Text Kit来实现的。如果在开发过程中需要自定义UITextView的布局过程,则可以使用Text Kit。</p>
<p> Text Kit是UIKit框架中的一组类和协议,主要是提供高质量的排版服务,让应用程序能够存储、布局和显示具有精细排版的文本,例如字距调整、连字、换行和对齐。Text Kit构建在Core Text之上,因此它具有与Core Text相同的速度和功率。 </p>
<p> UITextView与Text Kit完美结合,其中UITextView提供了编辑和显示功能,使用户能够输入文本,指定格式属性和查看结果;Text Kit类提供文本存储和布局功能。</p>
<p>下图显示了Text Kit在iOS文本控件和图形框架之间的位置:</p>
1.png<h1> Text Kit中的主要对象</h1>
<p> 下图显示了Text Kit中主要类的数据流路径,其中 text views 是UITextView实例,text containers 是NSTextContainer实例,layout manager 是NSLayoutManager实例, text storage是NSTextStorage实例. 在Text Kit中,NSTextStorage对像用来存储供UITextView显示的文本,并由NSLayoutManager对象布局到由NSTextContainer对象定义的区域中</p>
2.png<h3>NSTextContainer</h3>
<p> NSTextContainer对象定义可以布置文本的区域。通常,text container定义一个矩形区域,但是通过创建NSTextContainer的子类,您可以创建其他形状:例如圆形,五边形或不规则形状。一个文本容器不仅描述了一个可以填充文本的区域的轮廓,而且还保留了一个Bezier路径数组,该区域是其区域内未排除文本的排除区域。根据布局,文本在排除路径周围流动,提供了包括图形和其他非文本布局元素的手段。</p>
<h3>NSTextStorage</h3>
<p>NSTextStorage定义了Text Kit扩展文本处理系统的基本存储机制。 NSTextStorage是NSMutableAttributedString的一个子类,用于存储由文本系统操纵的字符和属性。它确保文本和属性在编辑操作之间保持一致的状态。除了存储文本之外,NSTextStorage对象管理一组客户端NSLayoutManager对象,通知它们对其字符或属性的任何更改,以便它们可以根据需要中继和重新显示文本。</p>
<h3>NSLayoutManager</h3>
<p>NSLayoutManager对象协调其他文本处理对象的操作。它在将NSTextStorage对象中的数据转换为视图显示区域中的呈现文本的操作中进行了介入。它将Unicode字符代码映射到字形,并监视由NSTextContainer对象定义的区域内的字形的布局。在布局过程中其执行以下操作:
<li>控制文本存储和文本容器对象</li>
<li>从字符生成字形</li>
<li>计算字形位置并存储信息</li>
<li>管理字形和字符的范围</li>
<li>在视图请求时,在文本视图中绘制字形</li>
<li>计算文本行的边界框矩形</li>
<li>控制连字符</li>
<li>操纵字符属性和字形属性</li>
</p>
<p>如果按照MVC的分工,NSLayoutManager是控制器,NSTextStorage是View,NSTextContainer则扮演Model的角色,UITextView是用来显示文本的视图。 </p>
<p>NSLayoutManager用作Text Kit的控制器,因为它将文本存储对象中的字符转换为字形,根据一个或多个文本容器对象的尺寸将其排列成行,并在一个或多个文本视图对象中协调文本显示。</p>
<p>编程时,对一个text storage对象进行编辑时,分为3个阶段:
1.发送一个beginEditing消息来宣布一组更改。
2.发送一个编辑信息,如replaceCharactersInRange:withString: 或者 setAttributes:range:来实现 字符或者属性的更改,每次发送这样的消息时,text storage对像将调用edited:range:changeInLength: 方法用于追踪从接收到beginEditing消息后受影响的字符的范围。
3.完成更改文本存储对象后,发送一条endEditing消息。text storage这时会给代理发消息textStorage:willProcessEditing:range:changeInLength:并调用其自己的processEditing方法,修改已更改字符的记录范围内的属性。
最后text storage会向每个关联的布局管理器发送消息 ,布局管理员反过来使用这些信息来重新计算它们的字形位置,并在必要时重新显示。</p>
<h1>应用</h1>
在最简单的情况下,Text Kit对象单独配置,即一个文本存储对象,一个文本容器和一个布局管理器,对应如下图:
4.png
如果要实现分页的话模型如下:
5.png
即一个LayoutManager对应多个TextContainer
代码实现如下:
let textStorage=NSTextStorage.init(string: "当初的少年,自信而且潜力无可估量,不知让得多少少女对其春心荡漾,当然,这也包括以前的萧媚。然而天才的道路,貌似总是曲折的,三年之前,这名声望达到巅峰的天才少年,却是突兀的接受到了有生以来最残酷的打击,不仅辛辛苦苦修炼十数载方才凝聚的斗之气旋,一夜之间,化为乌有,而且体内的斗之气,也是随着时间的流逝,变得诡异的越来越少。斗之气消失的直接结果,便是导致其实力不断的后退。从天才的神坛,一夜跌落到了连普通人都不如的地步,这种打击,让得少年从此失魂落魄,天才之名,也是逐渐的被不屑与嘲讽所替代。");
textStorage.addAttributes([NSFontAttributeName:UIFont.systemFont(ofSize: 18)], range: NSRange.init(location: 0, length: 1));
textStorage.addAttributes([NSFontAttributeName:UIFont.systemFont(ofSize: 16),NSForegroundColorAttributeName:UIColor.darkGray], range: NSRange.init(location: 1, length: textStorage.string.characters.count-1));
let layoutManager=NSLayoutManager.init();
textStorage.addLayoutManager(layoutManager);
let textContainer1=NSTextContainer.init()
layoutManager.addTextContainer(textContainer1);
self.textView1 = UITextView.init(frame: CGRect.zero, textContainer: textContainer1);
self.textView1.backgroundColor = UIColor.init(colorLiteralRed: 0, green: 110/255, blue: 101/255, alpha: 0.1);
self.view.addSubview(self.textView1);
self.textView1.isScrollEnabled = false;
self.textView1.snp.makeConstraints { (maker) in
maker.left.equalTo(10);
maker.top.equalTo(40);
maker.height.equalTo(205);
}
let textContainer2=NSTextContainer.init()
layoutManager.addTextContainer(textContainer2);
self.textView2 = UITextView.init(frame: CGRect.zero, textContainer: textContainer2);
self.textView2.backgroundColor = UIColor.init(colorLiteralRed: 1, green: 110/255, blue: 101/255, alpha: 0.1);
self.view.addSubview(self.textView2);
self.textView2.isScrollEnabled = true;
self.textView2.showsVerticalScrollIndicator = true;
self.textView2.snp.makeConstraints { (maker) in
maker.left.equalTo(self.textView1.snp.right).offset(10);
maker.top.equalTo(40);
maker.height.equalTo(205);
maker.width.equalTo(self.textView1.snp.width)
maker.right.equalTo(-10)
}
6.png
网友评论