TextKit

作者: Rui哥 | 来源:发表于2018-05-20 17:57 被阅读38次

    原文参考

    一、 基本的TextKit对象

    基本的TextKit对象
    • NSTextStorage 存储用于显示的文本。

    • NSLayoutManager 将NSTextStorage 中存储的文本显示在 NSTextContainer 所指定的区域内。

    • 通常NSTextContainer 定义文本显示的区域,通常是矩形区域,但是可以通过继承它而指定圆形、五边形等。 NSTextContainer 不仅定义了文本显示区域的轮廓,也维护了一个贝塞尔路劲的数组以定义不可布局的文本的区域。因此在布局的时候,文本流会围绕不可布局的路劲,以引入graphics及非文本布,局的因素。

    • NSTextStorage 是NSMutableAttributeString 的子类,是存储文本及其属性的基础存储机制。确保文本在编辑过程中处于一致的状态。 除了存储文本外,NSTextStorage 也管理一组 client NSLayoutMnager 对象,对其字符发生的任何变化,对这些Manager进行通知,以对文本及时的 relayout 和 redisplay。

    • NSLayoutManager 整体调度其它文本处理对象的操作,调解将NSTextStorage 中的数据到View 显示的区域的文本的所有的操作过程。他将 Unicode 字符转换成glyph(象形文字)并监督glyph 在NSTextContainer 对象所定义的区域中的布局过程。需要注意的是, layOutManager,textStorage, textContainer 可以从子线程访问,但是要保证单个线程访问安全。

    二、文本属性

    • Textkit 处理3种文本属性: 字符属性、paragraph属性、文档属性

    • 字符属性包括 font、颜色、和下标等特性,即单个字符或者一组字符的相关属性。

    • paragraph 属性是比如缩进、指标符和line spacing 等属性。

    • 文档属性包括纸张的大小,页边距,和视角放大百分比等文档属性。

    • 字符属性
      attributed字符串使用NSDictionary存储键值对形式的字符属性,key是字符串常量表示的属性名,比如NSFontAttributeName
      attributed string的组成
      概念上,attributed string中的每个字符都对应一个属性dictionary,但这组属性通常是针对一连串的字符串的,可能通过方法获取某字符或者某串字符对应的属性

    可以对attributed string应用任意自定义的key-value对,可以对NSTextStorage对象中的文本应用NSMutableAttributedString的addAttribute:value:range:方法以添加属性,也可以通过addAttributes:range:添加一组自定义属性。要支持自定义的属性,需要继续NSLayoutManager,重载drawGlyphsForGlyphRange:atPoint:,可以在此方法中先调用super将各字符先绘制出来然后将自己的属性在其上绘制出来,也可以完全自己绘制glyphs。

    • 段落属性

    paragraph属性影响段落中各行的排列,使用NSParagraphStyle类对象封装段落属性,NSParagraphStyleAttributeName这个字符串属性指定段落属性对象,attribute fixing这一机制会确保在编辑过程中,每个段落只对应一个NSParagraphStyle对象。

    • 文档属性

    虽然文本系统没有内建机制存储文档属性,但可以通过NSAttributedString的类似于initWithRTF:documentAttributes:的方法指定从一段RTF或者HTML数据流中提取的文档属性,相反地可以通过比如RTFFromRange:documentAttributes:的方法将RTF数据写入。

    attribute fixing

    为了处理编辑过程中产生的不一致,NSMutableAttributedString的UIKit扩展定义了fixAttributesInRange:方法来修复attachment,字符,段落属性之间的不一致。确保attachments在对应的attachment字符串删除之后不再存在,字符属性只应用于对应font可作用的字符上,且段落属性在整个段落中一致。

    三、 操作text storage

    • 要编辑text storage,需要3个步骤,先用beginEditing声明更改开始,然后用replaceCharactersInRange:withString: 和 setAttributes:range:类似的方法添加修改,每次调用这样的方法,text storage都会调用edited:range:changeInLength:以跟踪受其影响的字符。完成修改时,调用endEditing,这会导致其delegate调用到textStorage:willProcessEditing:range:changeInLength:,并调用其自己的processEditing方法,完成attribute fixing。

    修复完attributes后,会调用delegate的textStorage:didProcessEditing:range:changeInLength:方法以给予delegate验证及修改attribute的机会(虽然delegate可以改变字符属性,但它会引起attribute不一致)。最终text storage对所有相关的layout manager发送processEditingForTextStorage:edited:range:changeInLength:invalidatedRange:,通知这些range属性的变化,便于重新布局。

    四、布局文本

    • layout 过程

    • layoutmanager通过两个步骤控制文本的布局:glyph生成和glyph布局,这两个步骤都是lazily进行的,在生成了glyph并计算出其位置信息之后,会存起来以备以后使用,并监听glyph range的invalidated。

    • character range可以有两种方式自动失效:需要glyphs生成时和需要glyphs布局时。当然也可以手动失效glyph或者布局信息,当成layout manager收到获取失效区域glyph或者布局信息的时候,会重新生成glyph或者重新布局。

    • 生成行fragment rectangle

    text container中各行是由其形状及不可布局的路径所决定的,一旦行fragment 矩形与不可布局的路径相交,这些部分的行必须被缩短或者fragmented。如果区域中有gap,则重叠于其上的行必须进行偏移。

    layout manger提出一个矩形给某行,并请求text container调整这个矩形,这个矩形可以全部或者部分地超出text container的区域,但其可以变宽也可以变窄,请求调整的方法是lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:,其返回值是所请求区域中基于文本方向的最大的可用区域,它同时也会返回一个包含所有剩余区域的矩形,比如hole或者gap另一边的空间。

    在将文本适配进矩形之后,会做最后一个调整,称为line fragment padding,定义每行尾在行fragment矩形中的空白比例,可以通过lineFragmentPadding属性更改padding,但这并不是一个设置页边距的合适方法,可以设置textview在其父view中的位置,而对于text margin,可以设置textContainerInset属性,当然也可以设置段落的indent。

    除了line fragment矩形本身,layout manger还会返回一个称为used rectangle,这是line fragment矩形中实际显示glyph和mark的区域。通常两个矩形都包括line fragment padding,和行间空间(比如font的line height和段落的行spacing参数)。然而段落spacing及任何text周围的空白,及center-spaced文本引起的空白,只在行fragment矩形中出现,并不出现在used rectangle中。

    • 指定不可布局的路径

    text container维护着一个贝塞尔路径对象数组代表bounding区域中不可布局的区域。当lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:所请求的区域与这些路径包围的区域重叠的时候,会返回调整过的区域。

    行fragment适配
    • 指定多页和多列布局

    最简单的情况下,text kit对象都是单个的,即一个text storage object,一个text container,一个layout manager

    单文本流的对象配置

    但只有一个container和storage,这种安排下,文本流在textcontainer定义的空间中连续。但page breaks,多列布局,和更复杂的布局是无法由这种安排满足的。

    通过使用多text container,每个联系一个text view,可以实现更复杂的文本布局,比如可以通过这样实现page break

    分页文本配置
    • 多列文档的对象结构可以是这样
    多列文本对象配置

    与其每页单独对应一个text container,现在对应两个text container,页中每列一个。每个container控制文档的一部分,文本先是在左上的container中,满了之后delegate会收到通知,如果还有文本需要排布会在下一个text container中布局,并在完成的时候通知delegate,依此类推。

    不仅可以有多container,还可以有多个layout manager 访问同一个text storage,目的是提供同一段文本的多个view,如果用户更改了上面view中的文本,下面的view会直接反映出来

    同一段文本的多个布局

    总结

    • NSTextStorage 是 NSMutableAttributedString 子类,存储用于显示的文本,同时也管理一组 NSLayoutManager 对象
    • NSLayoutManager 负责将 NSTextStorage 中的文本显示到NSTextContainer 区域。它将 Unicode 字符转换成 glyph,并监督 glyph 在 NSTextContainer 对象所定义的区域中布局的过程。
    • ** NSTextContainer 负责保存文本显示的区域。**

    富文本点击事件主题思路

    • 将文本按照需求显示(一个一个Range 规划好显示)
    • 存储好各个类型的字体的range (如果,文本中url 的 range信息)
    • 根据手势点击确定 触摸点 所在文本的index (序号)
    • 根据index 和 存储的range比较 得出点击事件属于哪个range
    • 根据range 获取对应的subString,执行相应的相应事件。
      ok

    亦可参考
    demo

    相关文章

      网友评论

          本文标题:TextKit

          本文链接:https://www.haomeiwen.com/subject/zeytjftx.html