TextKit框架详细解析 (十三) —— 文本编程指南之使用TextKit绘制和管理文本(九)

版本记录

版本号 时间
V1.0 2018.09.01

前言

TextKit框架是对Core Text的封装,用简洁的调用方式实现了大部分Core Text的功能。 TextKit是一个偏上层的开发框架,在iOS7以上可用,使用它可以方便灵活处理复杂的文本布局,满足开发中对文本布局的各种复杂需求。TextKit实际上是基于CoreText的一个上层框架,其是面向对象的。接下来几篇我们就一起看一下这个框架。感兴趣的看下面几篇文章。
1. TextKit框架详细解析 (一) —— 基本概览和应用场景(一)
2. TextKit框架详细解析 (二) —— 基本概览和应用场景(二)
3. TextKit框架详细解析 (三) —— 一个简单布局示例(一)
4. TextKit框架详细解析 (四) —— 一个简单布局示例(二)
5. TextKit框架详细解析 (五) —— 文本编程指南之简介(一)
6. TextKit框架详细解析 (六) —— 文本编程指南之展示文本内容(二)
7. TextKit框架详细解析 (七) —— 文本编程指南之排版概念(三)
8. TextKit框架详细解析 (八) —— 文本编程指南之管理Text Fields and Text Views(四)
9. TextKit框架详细解析 (九) —— 文本编程指南之管理键盘(五)
10. TextKit框架详细解析 (十) —— 文本编程指南之复制、剪切和粘贴操作(六)
11. TextKit框架详细解析 (十一) —— 文本编程指南之输入数据的自定义视图(七)
12. TextKit框架详细解析 (十二) —— 文本编程指南之展示和管理编辑菜单(八)

Using Text Kit to Draw and Manage Text - 使用TextKit绘制和管理文本

UIKit框架包括几个类,其目的是在应用程序的用户界面中显示文本:UITextViewUITextFieldUILabel,如在Displaying Text Content in iOS中所述。从UITextView类创建的文本视图旨在显示大量文本。底层UITextView是一个名为Text Kit的强大布局引擎。如果您需要自定义布局过程或需要干预该行为,则可以使用Text Kit。对于需要自定义解决方案的少量文本和特殊需求,您可以使用替代的底层技术,如 Lower Level Text-Handling Technologies中所述。

Text Kit是UIKit框架中的一组类和协议,提供高质量的排版服务,使应用程序能够存储,布局和显示具有精细排版的所有特征的文本,例如字距调整,连字,断行和对齐。 Text Kit构建于Core Text之上,因此它提供相同的速度和功能。 UITextViewText Kit完全集成;它提供编辑和显示功能,使用户能够输入文本,指定格式属性和查看结果。其他Text Kit类提供文本存储和布局功能。图8-1显示了Text Kit在其他iOS文本和图形框架中的位置。

Figure 8-1 Text Kit Framework Position

Text Kit使您可以完全控制用户界面元素中的文本呈现。 除了UITextView之外,UITextFieldUILabel都构建在Text Kit之上,它与动画,UICollectionViewUITableView无缝集成。 Text Kit采用完全可扩展的面向对象架构设计,支持子类化,代理和一组完整的通知,支持深度自定义。


Primary Text Kit Objects - 主要Text Kit对象

主要Text Kit对象之间的数据流路径如图8-2所示。 Text viewUITextView类的实例,文本容器是NSTextContainer类的实例,布局管理器是NSLayoutManager类的实例,文本存储是NSTextStorage类的实例。 在Text Kit中,NSTextStorage对象存储由UITextView对象显示的文本,并由NSLayoutManager对象布置到由NSTextContainer对象定义的区域中。

Figure 8-2 Primary Text Kit Objects

NSTextContainer对象定义了可以布局文本的区域。通常,文本容器定义矩形区域,但通过创建NSTextContainer的子类,您可以创建其他形状:例如圆形,五边形或不规则形状。文本容器不仅描述了可以用文本填充的区域的轮廓,还维护了一个Bezier路径阵列,这些路径是其区域内没有布局文本的禁区。在布局时,文本围绕排除路径流动,提供了包含图形和其他非文本布局元素的方法。

NSTextStorage定义了Text Kit扩展文本处理系统的基本存储机制。 NSTextStorageNSMutableAttributedString的子类,用于存储由文本系统操纵的字符和属性。它确保文本和属性在编辑操作中保持一致的状态。除了存储文本之外,NSTextStorage对象还管理一组客户端NSLayoutManager对象,通知它们对其字符或属性的任何更改,以便它们可以根据需要中继和重新显示文本。

NSLayoutManager对象编排其他文本处理对象的操作。它在将NSTextStorage对象中的数据转换为视图显示区域中的渲染文本的操作中进行干预。它将Unicode字符代码映射到字形,并监视由NSTextContainer对象定义的区域内的字形布局。

注意:只要应用程序保证从单个线程访问,就可以从子线程访问NLayoutManagerNSTextStorageNSTextContainer

有关UITextView的参考信息,请参阅UITextView Class ReferenceNSTextContainerNSTextContainer Class Reference for iOSNSLayoutManagerNSLayoutManager Class Reference for iOSNSTextStorageNSTextStorage Class Reference for iOS中描述。


Text Attributes - 文本属性

Text Kit处理三种文本属性:字符属性,段落属性和文档属性(character attributes, paragraph attributes, and document attributes)。 字符属性包括诸如字体,颜色和下标之类的特征,这些特征可以与单个字符或一系列字符相关联。 段落属性是缩进,制表符和行间距等特征。 文档属性包括文档范围的特征,如纸张大小,边距和视图缩放百分比。

1. Character Attributes - 字符属性

属性字符串将字符属性存储为NSDictionary对象中的键值对。 键是属性名称,由标识符(NSString常量)表示,例如NSFontAttributeName。 图8-3显示了一个属性字符串,其中属性字典应用于字符串中的范围。

Figure 8-3 Composition of an attributed string

从概念上讲,属性字符串中的每个字符都有一个关联的属性字典。但是,通常,属性字典适用于较长范围的字符,即一行文本。 NSAttributedString类提供了获取字符索引并返回关联属性字典及其属性值应用范围的方法,例如attributesAtIndex:effectiveRange:

除了使用预定义属性外,您还可以将您选择的任何属性键值对分配给一个区间的字符。使用NSMutableAttributedString方法addAttribute:value:range:将属性添加到NSTextStorage对象中的相应字符范围。您还可以创建一个NSDictionary对象,其中包含一组自定义属性的名称和值,并使用addAttributes:range:方法在一个步骤中将它们添加到字符范围。要使用自定义属性,需要NSLayoutManager的自定义子类来使用它们。您的子类应重写[drawGlyphsForGlyphRange:atPoint:](https://developer.apple.com/documentation/appkit/nslayoutmanager/1403158-drawglyphsforglyphrange)方法。您的重写可以先调用超类来绘制字形范围,然后在顶部绘制自己的属性。或者,您的重写可以完全以您自己的方式绘制字形。

2. Paragraph Attributes - 段落属性

段落属性会影响布局管理器将文本行排列到页面上的段落中的方式。 文本系统将段落属性封装在NSParagraphStyle类的对象中。 其中一个预定义字符属性NSParagraphStyleAttributeName的值指向包含该字符范围的段落属性的NSParagraphStyle对象。 属性修复确保只有一个NSParagraphStyle对象与每个段落中的字符相关。

段落属性包括诸如对齐,制表位,换行模式和行间距(也称为前导)等特征。

3. Document Attributes - 文档属性

文档属性与文档整体相关。 文档属性包括纸张大小,边距和视图缩放百分比等特征。 尽管文本系统没有用于存储文档属性的内置机制,但NSAttributedString初始化方法(如initWithRTF:documentAttributes:)可以填充您提供的NSDictionary对象,其中包含从RTFHTML数据流派生的文档属性。 相反,如果传递对包含消息的NSDictionary对象的引用,则编写RTF数据的方法(如RTFFromRange:documentAttributes:)会写入文档属性。

4. Attribute Fixing

编辑属性字符串可能会导致必须通过attribute fixing来清除不一致性。 NSMutableAttributedString的UIKit扩展定义了fixAttributesInRange:方法来修复附件,字符和段落属性之间的不一致。 这些方法可确保在删除附件字符后不会保留附件,字符属性仅适用于该字体中可用的字符,并且段落属性在各段落中都是一致的。


Changing Text Storage Programmatically - 以编程方式更改文本存储

NSTextStorage对象充当Text Kit的字符数据存储库。此数据的格式是属性字符串attributed string,它是一系列字符(Unicode编码)和相关属性(如字体,颜色和段落样式)。表示属性字符串的类是NSAttributedStringNSMutableAttributedString,其中NSTextStorage是子类。如Character Attributes中所述,文本块中的每个字符都有一个键和与之关联的值的字典。键命名属性(例如NSFontAttributeName),关联值指定该属性的特征(例如Helvetica 12-point)。

以编程方式编辑文本存储对象有三个阶段。第一阶段是向它发送beginEditing消息以宣布一组更改。

在第二阶段,您向它发送一些编辑消息,例如replaceCharactersInRange:withString:setAttributes:range:,以实现字符或属性的更改。每次发送这样的消息时,文本存储对象都会调用edited:range:changeInLength:跟踪自收到beginEditing消息以来受影响的字符范围。

在第三阶段,当您完成文本存储对象的更改后,将向其发送endEditing消息。这使它发出代理消息textStorage:willProcessEditing:range:changeInLength:并调用自己的processEditing方法,修复记录的已更改字符范围内的属性。有关属性修复的信息,请参见Attribute Fixing

在修复其属性之后,文本存储对象发送代理方textStorage:didProcessEditing:range:changeInLength:,为委托者提供验证并可能更改属性的机会。 (尽管代理可以在此方法中更改文本存储对象的字符属性,但它不能在不使文本存储处于不一致状态的情况下更改字符本身。)最后,文本存储对象发送processEditingForTextStorage:edited:range:changeInLength:invalidatedRange:消息到每个关联的布局管理器 - 指示文本存储对象中已更改的范围,以及这些更改的性质。布局管理器反过来使用此信息重新计算其字形位置,并在必要时重新显示。


Working with Font Objects - 使用字体对象

计算机字体是OpenTypeTrueType等格式的数据文件,包含描述一组字形的信息,如 Characters and Glyphs中所述,以及字形渲染中使用的各种补充信息。 UIFont类提供了获取和设置字体信息的接口。 UIFont实例提供对字体特征和字形的访问。 Text Kit将字符信息与字体信息组合在一起,以选择文本布局期间使用的字形。您可以通过将字体对象传递给接受它们作为参数的方法来使用它们。字体对象是不可变的,因此可以安全地从应用程序中的多个线程中使用它们。

您不使用allocinit方法创建UIFont对象;相反,您使用preferredFontForTextStyle:文本样式常量或fontWithName:size:。您还可以使用字体描述符使用fontWithDescriptor:size:来创建字体。这些方法检查具有指定特征的现有字体对象,如果有,则返回它。否则,他们会查找请求的字体数据并创建相应的字体对象。

1. Text Styles - 文本样式

iOS 7中引入的文本样式是字体的预期用途的语义描述,并通过称为动态类型(Dynamic Type)的机制实现。 文本样式按使用组织,并由UIFontDescriptor.h中定义的常量表示,如表8-1所示。 用于由文本样式描述的目的的实际字体可以基于许多动态考虑因素而变化,包括用户的内容大小类别首选项,其由UIApplication属性preferredContentSizeCategory表示。 要获取给定文本样式的字体对象,请将相应的常量传递给UIFont方法preferredFontForTextStyle:。 要获取文本样式的字体描述符,请将常量传递给UIFontDescriptor方法preferredFontDescriptorWithTextStyle:。 (有关字体描述符的更多信息,请参阅Using Font Descriptors。)

Table 8-1 Text style constants

文本样式通过动态类型机制为应用程序带来许多优势,所有这些都增强了文本的可读性。动态类型以协调的方式响应用户首选项,并响应可访问性设置以增强易读性和超大类型。也就是说,当您调用preferredFontForTextStyle:时,返回的特定字体包括根据用户首选项和上下文而变化的特征,包括跟踪(字母间距)调整,以及针对特定文本样式常量指定的用途进行调整。

使用文本样式常量返回的字体旨在用于应用程序中除用户界面元素中的文本之外的所有文本,例如buttons, bars, and labels。当然,您需要选择在您的应用中看起来正确的文字样式。观察UIContentSizeCategoryDidChangeNotification也很重要,这样您就可以在用户更改内容大小类别时重新布局文本。当您的应用收到该通知时,它应该将invalidateIntrinsicContentSize消息发送到由Auto Layout定位的视图,或者将setNeedsLayout发送到手动定位的用户界面元素。它应该使首选字体或字体描述符无效并根据需要获取新字体。

2. Using Font Descriptors - 使用字体描述符

UIFontDescriptor类实例化的字体描述符提供了一种使用属性字典描述字体的方法,并用于创建UIFont对象。特别是,您可以从字体描述符创建UIFont对象,您可以从UIFont对象获取描述符,并且您可以更改描述符并使用它来创建新的字体对象。您还可以使用字体描述符来指定应用程序提供的自定义字体。

字体描述符可以存档,这是使用文本样式的一个优点。您不应该缓存由文本样式指定的字体对象,因为它们是动态的 - 它们的特性会根据用户首选项随时间变化。但是您可以缓存字体描述符以保留字体的描述,然后在以后取消存档并使用它来创建具有相同特征的字体对象。

您可以使用字体描述符在系统中查询与特定属性匹配的可用字体,然后创建与这些属性匹配的字体实例,例如名称,特征,语言和其他功能。例如,您可以使用字体描述符来检索与给定字体系列名称匹配的所有字体,使用CSS标准定义的系列名称,如Listing 8-1所示。

// Listing 8-1  Font family name matching

UIFontDescriptor *helveticaNeueFamily =
    [UIFontDescriptor fontDescriptorWithFontAttributes:
        @{ UIFontDescriptorFamilyAttribute: @"Helvetica Neue" }];
NSArray *matches =
    [helveticaNeueFamily matchingFontDescriptorsWithMandatoryKeys: nil];

如上所示,matchingFontDescriptorsWithMandatoryKeys:方法返回系统中所有Helvetica Neue字体的字体描述符数组,例如HelveticaNeueHelveticaNeue-MediumHelveticaNeue-LightHelveticaNeue-Thin等。

您可以通过应用符号特征,例如粗体,斜体,展开和浓缩修改preferredFontForTextStyle:返回的字体。 您可以使用字体描述符来修改特定的特征,如Listing 8-2所示。

// Listing 8-2  Font trait modification

UIFontDescriptor *fontDescriptor =
    [UIFontDescriptor preferredFontDescriptorWithTextStyle: UIFontTextStyleBody];
UIFontDescriptor *boldFontDescriptor =
    [fontDescriptor fontDescriptorWithSymbolicTraits: UIFontDescriptorTraitBold];
UIFont *boldFont = [UIFont fontWithDescriptor: boldFontDescriptor size: 0.0];

此代码段首先检索正文文本样式的字体描述符,然后修改该字体描述符以指定粗体特征,最后使用UIFont类方法fontWithDescriptor:size:以粗体返回正文文本样式的实际字体对象特征。 使用fontWithDescriptor:size:传递size大小值为0.0指定保留最初使用字体描述符返回的size属性。 当然,这种行为是必需的,因为字体大小由动态类型机制决定。

3. Activating Font Features - 激活字体特征

字体描述符的另一个重要用途是激活和选择字体特征。字体特征是字体的排版属性,当字体由文本系统呈现时控制其外观的各个方面。仅当字体设计者选择包含字体时,字体特征才可用于字体。某些字体特征以少数字体提供,而其他字体则适用于许多字体。此外,安装在不同平台上的相同字体的不同版本可以在其可用字体特征方面有所不同。

字体特征分为称为feature types的类别,其中单个特征选择器选择特定特征设置。特征类型可以是独占的,也可以是非独占的。如果特征类型是独占的,则您一次只能选择一个可用的特征选择器,例如数字是比例还是固定宽度。如果特征类型为非排他性,则可以一次启用任意数量的特征选择器。例如,对于连字特征类型,您可以选择字体支持的可用连字类的任意组合。

注意:如果选择字体中不可用的特征,则不会看到字体字形外观发生变化。

一些特征是上下文的,而另一些特征是非上下文的。将上下文特征应用于字形的方式取决于字形与相邻字形相比的位置。文本系统布局功能的一个强大方面是它能够自动应用复杂的上下文处理。

无论相邻的字形如何,非上下文特征(Noncontextual features )都以相同的方式应用于字形。这些特征包括选择替代字形集,以便为文本提供不同的外观和字形替换,以达到数学排版或增强排版复杂性的目的。

例如,Listing 8-3中的代码激活了Helvetica Neue Medium字体定义的两种特征类型。

// Listing 8-3  Activating Font Features

NSArray *timeFeatureSettings = @[
  @{
    UIFontFeatureTypeIdentifierKey: @(kNumberSpacingType),
    UIFontFeatureSelectorIdentifierKey: @(kProportionalNumbersSelector)
  },
  @{
    UIFontFeatureTypeIdentifierKey: @(kCharacterAlternativesType),
    UIFontFeatureSelectorIdentifierKey: @(2)
  }];
 
UIFont *originalFont = [NSFont fontWithName: @"HelveticaNeue-Medium" size: 12.0];
UIFontDescriptor *originalDescriptor = [originalFont fontDescriptor];
UIFontDescriptor *timeDescriptor = [originalDescriptor
    fontDescriptorByAddingAttributes: @{
        UIFontDescriptorFeatureSettingsAttribute: timeFeatureSettings }];
UIFont *timeFont = [UIFont fontWithDescriptor: timeDescriptor size: 12.0];

Listing 8-3中的代码激活数字间距特征类型(由常量kNumberSpacingType表示),选择比例宽度数字(kProportionalNumbersSelector)和字符替代特征类型(kCharacterAlternativesType),其中特征选择器标识符键值为2。 此示例中用于表示字体特征类型和选择器的常量在Core Text框架(CoreText / CoreText.h)中的头文件SFNTLayoutTypes.h中声明为枚举。 对于字符替换类型,没有预定义常量来表示特征选择器标识符,因此您只需使用字体定义的数值。

由于字体功能是由字体定义的,因此确定支持特征的最可靠方法是直接查询字体。 您可以使用Core Text中的CTFontCopyFeatures函数执行此操作,如Listing 8-4所示。

// Listing 8-4  Querying Font Features

UIFont *font = [UIFont fontWithName: @"HelveticaNeue-Medium" size: 12.0];
CFArrayRef fontFeatures = CTFontCopyFeatures((__bridge CTFontRef) font);
NSLog(@"properties = %@", fontFeatures);

Listing 8-5显示了Listing 8-4CTFontCopyFeatures函数产生的字体特征数组,因为它显示在控制台日志中。

// Listing 8-5  Typical Result from CTFontCopyFeatures Function

properties = (
        {
        CTFeatureTypeExclusive = 1;
        CTFeatureTypeIdentifier = 6;
        CTFeatureTypeName = "Number Spacing";
        CTFeatureTypeNameID = 266;
        CTFeatureTypeSelectors =         (
                        {
                CTFeatureSelectorDefault = 1;
                CTFeatureSelectorIdentifier = 0;
                CTFeatureSelectorName = "No Change";
                CTFeatureSelectorNameID = 264;
            },
                        {
                CTFeatureSelectorIdentifier = 1;
                CTFeatureSelectorName = "Proportional Numbers";
                CTFeatureSelectorNameID = 267;
            }
        );
    },
        {
        CTFeatureTypeExclusive = 1;
        CTFeatureTypeIdentifier = 17;
        CTFeatureTypeName = "Character Alternatives";
        CTFeatureTypeNameID = 262;
        CTFeatureTypeSelectors =         (
                        {
                CTFeatureSelectorDefault = 1;
                CTFeatureSelectorIdentifier = 0;
                CTFeatureSelectorName = "No Change";
                CTFeatureSelectorNameID = 264;
            },
                        {
                CTFeatureSelectorIdentifier = 1;
                CTFeatureSelectorName = "Alternate Punctuation";
                CTFeatureSelectorNameID = 263;
            },
                        {
                CTFeatureSelectorIdentifier = 2;
                CTFeatureSelectorName = "Numbers Punctuation";
                CTFeatureSelectorNameID = 265;
            }
        );
    }
)

在这种情况下,结果显示此版本的Helvetica Neue Medium字体具有两种字体特征:Number SpacingCharacter Alternatives。使用字体描述符激活字体特征并在其设置中进行选择时,此结果中最重要的值是特征类型标识符和特征选择器标识符。将这些值添加到表示字体特征设置的字典数组中,然后将该数组用作UIFontDescriptorFeatureSettingsAttribute的值,并依次传递给fontDescriptorByAddingAttributes:fontDescriptorWithFontAttributes:方法,如Listing 8-3所示。该列表中显示的常量的枚举值与CTFontCopyFeatures函数返回的特征类型标识符和特征选择器标识符的数值相关联。

Listing 8-5所示,CTFontCopyFeatures函数生​​成的字体特征数组还显示特征类型是否为独占,以及哪个特征selector是默认值。当然,功能类型名称和功能selector名称值为可用的字体特征及其设置提供了人类可读的标识。

4. Querying Font Metrics - 查询字体度量标准

当该信息可用时,UIFont定义了许多用于访问字体度量信息的方法。 诸如ascender, capHeight, xHeight等属性都对应于标准字体度量信息。 图8-4显示了字体度量如何应用于字形维度,表8-2列出了与各种度量相关的属性名称。 有关更多具体信息,请参阅属性说明。

Figure 8-4 Font metrics
Table 8-2 Font metrics and related UIFont methods

Laying Out Text - 布局文本

NSLayoutManager类实例化的布局管理器对象是Text Kit中文本显示的中央控制对象。布局管理器执行以下操作:

  • 控制文本存储和文本容器对象
  • 从字符生成字形
  • 计算字形位置并存储信息
  • 管理字形和字符的范围
  • 视图请求时在文本视图中绘制字形
  • 计算文本行的边界框矩形
  • 控制连字符
  • 处理字符属性和字形属性

在模型 - 视图 - 控制器范例中,布局管理器是控制器。 NSTextStorageNSMutableAttributedString的子类,它提供了模型的一部分,包含一串文本字符,其中包含字体,样式,颜色和大小等属性。 NSTextContainer也可以被视为模型的一部分,因为它模拟了布局文本的页面的几何布局。 UITextView(或其他UIView对象)提供显示文本的视图。 NSLayoutManager用作文本系统的控制器,因为它将文本存储对象中的字符转换为字形,根据一个或多个文本容器对象的尺寸将它们排成行,并在一个或多个文本视图对象中协调文本显示。

1. The Layout Process - 布局过程

布局管理器在两个单独的步骤中执行文本布局:字形生成和字形布局。布局管理器懒地执行两个布局步骤,即根据需要执行。因此,一些NSLayoutManager方法会导致字形生成,而其他方法则不会,字形布局也是如此。在生成字形并在计算其布局位置之后,布局管理器会缓存信息以提高后续调用的性能。

布局管理器缓存字形,属性和布局信息。它跟踪文本存储中字符更改失效的字形范围。有两种方法可以自动使字符范围无效:如果需要生成字形或者需要字符布局。如果您愿意,可以手动使字形或布局信息无效。当布局管理器收到需要知道字形或布局在无效范围内的消息时,它会生成字形或根据需要重新计算布局。

2. Generating Line Fragment Rectangles - 生成线段矩形

布局管理器在字形行中的NSTextContainer对象中布局文本。文本容器中这些行的布局由其形状和它包含的任何排除路径确定。无论线段矩形与由排除路径定义的区域相交,这些部分中的线都必须缩短或分段;如果整个区域之间存在间隙,则必须移动与其重叠的线以进行补偿。

布局管理器为给定的行提出一个矩形,然后要求文本容器调整矩形以适应。建议的矩形通常跨越文本容器的边界矩形,但它可以更窄或更宽,并且它也可以部分或完全位于边界矩形之外。布局管理器发送文本容器以调整建议的矩形的消息是lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:,它根据文本的布局方向返回可用于建议矩形的最大矩形。它还返回一个包含任何剩余空间的矩形,例如文本容器中孔或间隙另一侧留下的空间。

布局管理器在实际将文本插入矩形时进行最后一次调整。这个调整是由文本容器固定的一小部分,称为行片段填充(line fragment padding),它将行片段矩形每端的部分定义为空白。文本在行片段矩形内插入此量(矩形本身不受影响)。填充允许在边缘(以及任何孔周围)对文本容器区域进行小规模调整,并使文本不直接邻接区域附近显示的任何其他图形。您可以使用lineFragmentPadding属性更改默认值的填充。请注意,线段填充不是表达边距的合适方法。对于文档边距,应在其封闭视图中设置UITextView对象的位置和大小。对于文本边距,您应该设置文本视图的textContainerInset属性。此外,您可以使用NSMutableParagraphStyle属性(如headIndent)为各个段落设置缩进值。

除了返回线段矩形本身之外,布局管理器还返回一个称为使用矩形(used rectangle)的矩形。这是线段矩形的一部分,实际上包含要绘制的字形或其他标记。按照惯例,两个矩形都包括行片段填充和行间距(根据字体的行高度度量和段落的行间距参数计算)。但是,段落间距(前后)和文本周围添加的任何空格(例如由中心间距文本引起的空间)仅包含在线段矩形中,并且不包含在使用的矩形中。

3. Specifying Exclusion Paths - 指定排除路径

文本容器维护一个UIBezierPath对象数组,表示接收器边界矩形内的排除路径。 当布局管理器向文本容器发送lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:消息,建议与排除路径定义的某个区域相交的线段矩形时,文本容器返回一个不包括该区域的调整后的线段矩形。 该过程如图8-6所示。

Figure 8-5 Line fragment fitting

4. Specifying Multipage and Multicolumn Layouts - 指定多页和多列布局

在最简单的情况下,Text Kit对象是单独配置的,即一个文本存储对象,一个文本容器和一个布局管理器,如图8-6所示。 从Interface Builder中的对象库拖动文本视图时,将自动实例化此配置。 UITextView对象会检出其他对象并将它们连接在一起。 您还可以在代码中创建此排列,如Listing 8-6所示。

Figure 8-6 Object configuration for a single text flow

您还可以在代码中创建此排列,如Listing 8-6所示。 此代码可以位于视图控制器中,例如,UIViewController的子类,它具有名为textContainerNSTextContainer属性。

// Listing 8-6  Object creation for a single text flow

NSTextStorage* textStorage = [[NSTextStorage alloc] initWithString:string];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];
self.textContainer = [[NSTextContainer alloc] initWithSize:self.view.bounds.size];
[layoutManager addTextContainer:self.textContainer];
UITextView* textView = [[UITextView alloc] initWithFrame:self.view.bounds textContainer:self.textContainer];
[self.view addSubview:textView];

仅具有一个文本容器和一个文本视图限制了此配置。 在这种安排中,文本在由文本容器定义的区域内不间断地流动。 这种安排无法满足分页符,多列布局和更复杂的布局。

通过使用多个文本容器,每个容器具有关联的文本视图,可以进行更复杂的布局安排。 例如,要支持分页符,应用程序可以配置文本对象,如图8-7所示。

Figure 8-7 Object configuration for paginated text

每个文本容器对应于文档的页面。 显示文本的视图可以嵌入到应用程序提供的自定义视图对象中,作为文本视图的背景。 反过来,此自定义视图可以嵌入到UIScrollView对象中,以使用户能够滚动文档的页面。

多列文档可以使用类似的对象排列建模,如图8-8所示。

Figure 8-8 Object configuration for multicolumn text

现在有两个文本容器 - 一个用于页面上的每一列,而不是让一个文本容器对应一个页面。每个文本容器控制文档的一部分。显示文本时,首先在左上角的容器中布置字形。当该视图中没有空间时,布局管理器通知其代理它已完成填充容器。代理可以检查是否有更多需要布局的文本,并在必要时添加另一个文本容器。布局管理器继续在下一个容器中布置文本,在完成时通知代理,依此类推。同样,自定义视图(描绘为蓝色矩形)为这些文本列提供了画布。

您不仅可以拥有多个文本容器,还可以拥有多个访问同一文本存储的NSLayoutManager对象。图8-9说明了具有多个布局管理器的对象布局。这种安排的效果是提供相同文本的多个视图。如果用户更改顶视图中的文本,则更改会立即反映在底部视图中(假设更改的位置在底部视图的边界内)。

Figure 8-9 Object configuration for multiple views of the same text

后记

本篇主要讲述了使用TextKit绘制和管理文本,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容