使用Text Kit包绘制和管理文本
UIKit框架包括几个用于在应用程序的用户界面中显示文本的类:UITextView、UITextField和UILabel,如Displaying Text Content in iOS中所述。从UITextView类创建的Text views用于显示大量文本。底层UITextView是一个强大的布局引擎,称为Text Kit。如果您需要自定义布局过程或需要干预该行为,则可以使用Text Kit。对于数量较少的文本和需要定制解决方案的特殊需求,可以使用替代的、较低级别的技术,如Lower Level Text-Handling Technologies中所述。
Text Kit是UIKit框架中的一组类和协议,提供高质量的排版服务,使应用程序能够存储,布局和显示具有良好排版的所有特征的文本,例如字间距(kerning),连体字(ligatures),line breaking和对齐(justification)。Text Kit是建立在Core Text之上,所以它提供相同的速度和能力。UITextView与Text Kit完全集成;它提供编辑和显示功能,允许用户输入文本、指定格式化属性和查看结果。其他Text Kit类提供文本存储和布局功能。Text Kit在其他iOS文本和图形框架中的位置如图8-1所示。
Figure 8-1 Text Kit Framework Position
Text Kit使您可以完全控制用户界面元素中的文本呈现。除了UITextView, UITextField和UILabel都是建立在Text Kit之上的,它与动画,UICollectionView和UITableView无缝集成。Text Kit采用完全可扩展的面向对象体系结构设计,支持子类化、委托和一组完整的通知,从而实现深度定制。
Text Kit主要对象
主要Text Kit对象之间的数据流路径如图8-2所示。Text views是UITextView类的实例,Text containers是NSTextContainer类的实例,layout manager是NSLayoutManager类的实例,文本存储是NSTextStorage类的实例。在Text Kit中,NSTextStorage对象存储文本,这个文本是由UITextView对象显示,并由NSLayoutManager对象布局,由NSTextContainer对象定义的区域的文本。
Figure 8-2 Primary Text Kit Objects
一个NSTextContainer对象定义了一个文本可以布局的区域。通常,文本容器定义了一个矩形区域,但是通过创建NSTextContainer的子类,您可以创建其他形状,例如圆形、五边形或不规则形状。文本容器不仅描述了一个可以用文本填充的区域的轮廓,还维护了一个贝塞尔路径数组,这些路径是其区域内不进行文本布局的排除区域。当进行布局时,文本会围绕这些排除路径流动,提供了一种包含图形和其他非文本布局元素的方式。
译者注:排除区域就是被贝塞尔曲线画出来不让文字布局的地方
NSTextStorage定义了Text Kit扩展文本处理系统的基本存储机制。NSTextStorage是NSMutableAttributedString的子类,用于存储文本系统操作的字符和属性。它确保文本和属性在编辑操作中保持一致的状态。除了存储文本之外,NSTextStorage对象还管理一组客户端NSLayoutManager对象,通知它们字符或属性的任何更改,以便它们可以根据需要传递和重新显示文本。
注意:只要应用程序能够保证从单个线程访问,即使通过子线程,也可以访问NSLayoutManager、NSTextStorage和NSTextContainer。(也就是你要一直通过一个线程访问,子线程和主线程都行)
For reference information about UITextView
, see UITextView Class Reference. NSTextContainer
is described in NSTextContainer Class Reference for iOS , NSLayoutManager
in NSLayoutManager Class Reference for iOS , and NSTextStorage
in NSTextStorage Class Reference for iOS.
文本属性(Text attributes)
Text Kit处理三种文本属性:字符属性(character)、段落属性(paragraph)和文档属性(document)。字符属性包括字体、颜色和下标等特征,可以与单个字符或多个字符相关联。段落属性包括缩进、制表符和行间距等特征。文档属性包括文档范围的属性,如纸张大小、边距和视图缩放百分比。
字符属性(Character Attributes)
属性字符串将字符属性作为键值对存储在NSDictionary对象中。键是由属性名称表示的标识符(NSString常量),例如NSFontAttributeName。图8-3显示了将带有属性字典的attributed string应用到一个字符串的一段中。
Figure 8-3 Composition of an attributed string
从概念上讲,属性字符串中的每个字符都有一个关联的属性字典。然而,通常一个属性字典适用于较长的字符区间,也就是一段文本。NSAttributedString类提供了在给定字符索引处返回关联的属性字典和属性值适用范围的方法,例如attributesAtIndex:effectiveRange:。
除了与预定义的属性一起工作外,您还可以为一段字符范围分配任何您选择的属性键值对。您将属性添加到NSTextStorage对象中适当的字符范围,使用addAttribute:value:range:方法。您还可以创建一个包含自定义属性名称和值的NSDictionary对象,并使用addAttributes:range:方法将它们添加到字符范围中。要使用您的自定义属性,您需要一个自定义的NSLayoutManager子类来处理它们。您的子类应该重写drawGlyphsForGlyphRange:atPoint:方法。您的覆盖可以首先调用父类来绘制字形范围,然后在上面绘制自己的属性。或者,您的覆盖可以完全按自己的方式绘制字形。
段落属性(Paragraph Attributes)
段落属性影响布局管理器将文本行排列成页面上的段落的方式。文本系统将段落属性封装在NSParagraphStyle类对象中。预定义字符属性之一NSParagraphStyleAttributeName的值指向一个NSParagraphStyle对象。属性固定确保每个段落只涉及一个NSParagraphStyle对象。
段落属性包括对齐、制表位、换行模式和行间距(也称为leading)等特征。
文档属性(Document Attributes)
文档属性与整个文档相关联。文档属性包括纸张大小、边距和视图缩放百分比等特征。虽然文本系统没有内置机制来存储文档属性,但是NSAttributedString初始化方法(如initWithRTF:documentAttributes:)可以从RTF或HTML数据流派生文档属性,并将它们填充到您提供的NSDictionary对象中。相反,如果您传递包含它们的NSDictionary对象的引用,那么写入RTF数据的方法(如RTFFromRange:documentAttributes:)也会写入文档属性。(这里看不懂去看下api的文档就明白了,苹果写的太拗口了)
属性固定(Attribute Fixing)
编辑属性字符串可能会导致不一致性,必须通过属性固定来清理这种不一致性。UIKit对NSMutableAttributedString的扩展定义了fixAttributesInRange:方法来修复附件、字符和段落属性之间的一致性问题。这些方法确保在删除附件字符后不会保留附件,确保字符属性仅适用于可用的字体中的字符,并确保整个段落中的段落属性是一致的。
通过编程方式更改文本存储(Text Storage)
NSTextStorage是Text Kit框架中的一个类,用于存储字符数据。这些数据以属性字符串的形式存在,即包含Unicode编码的字符序列及其相关属性(如字体、颜色和段落样式)。NSAttributedString和NSMutableAttributedString表示这些属性字符串,NSTextStorage是它们的子类。
在更改文本存储对象时,分为三个阶段:
- 首先,向其发送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:消息,指示文本存储对象中已更改的范围以及这些更改的性质。布局管理器会使用此信息重新计算字形位置,并在必要时进行重新显示。
使用字体对象(Font Objects)进行工作。
计算机字体是一种数据文件,格式为OpenType或TrueType等,包含描述一组字形的信息(如字符和字形中所述),以及用于字形渲染的各种补充信息。UIFont类提供了获取和设置字体信息的接口。UIFont实例提供对字体特征和字形的访问。Text Kit将字符信息与字体信息相结合,在文本布局期间选择使用的字形。字体对象是不可变的,因此在您的应用程序中从多个线程使用它们是安全的。
您不会使用alloc和init方法来创建UIFont对象;相反,您使用preferredFontForTextStyle:或fontWithName:size:。您还可以使用字体描述符来创建具有fontWithDescriptor:size:的字体。这些方法检查具有指定特征的现有字体对象,如果存在则返回它。否则,它们会查找所请求的字体数据并创建相应的字体对象。
文本样式(Text Styles)
文本样式(在iOS 7中引入)是对字体预期用途的语义描述,并通过称为动态类型 (Dynamic Type)的机制实现。文本样式按用途组织,并由UIFontDescriptor.h中定义的常量表示,如表8-1所示。实际字体可以根据许多动态因素而变化,包括用户的内容大小类别偏好,它由UIApplication属性preferredContentSizeCategory表示。要获取给定文本样式的字体对象,您可以将相应的常量传递给UIFont方法的preferredFontForTextStyle:。要获取文本样式的字体描述符,请将常量传递给UIFontDescriptor方法的preferredFontDescriptorWithTextStyle:。(这里的功能看下设置里面的动态字体体验一下就好了, 我手机是iOS 16系统,在系统设置-辅助功能-显示于文字大小-更大字体)
Table 8-1 Text style constants
Constant | Usage |
---|---|
UIFontTextStyleHeadline |
The font used for headings. |
UIFontTextStyleSubheadline |
The font used for subheads. |
UIFontTextStyleBody |
The font used for body text. |
UIFontTextStyleFootnote |
The font used for footnotes. |
UIFontTextStyleCaption1 |
The font used for standard captions. |
UIFontTextStyleCaption2 |
The font used for alternate captions. |
文本样式通过动态类型机制为应用程序带来了许多优势,所有这些优势都增强了文本的可读性。动态类型以协调的方式响应用户偏好,并响应辅助功能设置,以提高可读性和超大字体。也就是说,当调用preferredFontForTextStyle:时,返回的特定Font包括根据用户首选项和上下文而变化的特征,包括跟踪(字母间距)调整,以及针对特定文本样式常量指定的调优。
使用文本样式常量返回的字体旨在用于应用程序中除用户界面元素(如按钮、栏和标签)中的文本之外的所有文本。自然,您需要选择适合您应用程序的文本样式。观察UIContentSizeCategoryDidChangeNotification也很重要,这样当用户更改内容大小类别时,您可以重新布局文本。当您的应用程序收到该通知时,它应该向由Auto Layout布局的视图发送invalidateIntrinsicContentSize消息,或向手动布局的用户界面元素发送setNeedsLayout。并且它应该使首选字体或字体描述符无效,并根据需要获取新的字体或字体描述符。
Using Font Descriptors
字体描述符是从UIFontDescriptor类实例化的,提供了一种用属性字典描述字体的方法,并用于创建UIFont对象。特别是,您可以从字体描述符创建UIFont对象,从UIFont对象获取描述符,更改描述符并使用它创建新的字体对象。您还可以使用字体描述符来指定应用程序提供的自定义字体。
字体描述符可以进行归档,这是使用文本样式的一个优势。您不应该缓存由文本样式指定的字体对象,因为它们是动态的------它们的特征会随着时间的推移根据用户偏好而变化。但您可以缓存字体描述符以保留字体的描述,然后在稍后解压缩并使用它创建具有相同特征的字体对象。
您可以使用字体描述符查询系统以获取与特定属性匹配的可用字体,然后创建与这些属性匹配的字体实例,例如名称、特征、语言和其他功能。例如,您可以使用字体描述符检索与给定字体族名匹配的所有字体,使用CSS标准定义的族名,如清单8-1所示。
Listing 8-1 Font family name matching
ini
UIFontDescriptor *helveticaNeueFamily =
[UIFontDescriptor fontDescriptorWithFontAttributes:@{
UIFontDescriptorFamilyAttribute: @"Helvetica Neue"
}];
NSArray *matches =
[helveticaNeueFamily matchingFontDescriptorsWithMandatoryKeys: nil];
如图所示的matchingFontDescriptorsWithMandatoryKeys:方法返回一个数组,其中包含系统上所有Helvetica Neue字体的字体描述符,如HelveticaNeue、HelveticaNeue-Medium、HelveticaNeue-Light、HelveticaNeue-Thin等。
您可以通过应用符号特征,如粗体、斜体、扩展和紧缩,来修改preferredFontForTextStyle:返回的字体。您可以使用字体描述符来修改特定的特征,如清单8-2所示。
Listing 8-2 Font trait modification
ini
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:指定保留最初与Font描述符一起返回的size属性。当然,这种行为是需要的,因为Font大小是由动态类型机制决定的。
激活字体功能(Font Features)
字体描述符的另一个重要用途是激活和选择字体功能。字体功能是字体的排版属性,当字体的字形由文本系统呈现时,它们控制其外观的各个方面。只有在字体设计者选择包含它们时,字体功能才可用。一些字体功能在少数字体中可用,而另一些则在许多字体中常见。此外,安装在不同平台上的同一字体的不同版本可能在可用的字体功能方面有所不同。
字体功能被分成组后称为功能类型(feature types),在其中单个功能选择器选择特定的功能设置。功能类型可以是排他的或非排他的。如果功能类型是排他的,则一次只能选择一个可用的功能选择器,例如数字是等宽的还是固定宽度的。如果功能类型是非排他的,则可以同时启用任意数量的功能选择器。例如,对于连字功能类型,您可以选择字体支持的可用连字类的任意组合。
注意:如果您选择了字体中不可用的功能,您将不会看到字体字形外观的变化。
一些功能是上下文的,而另一些是非上下文的。上下文功能应用于字形的方式取决于字形与相邻字形的位置关系。文本系统布局功能的强大之处在于它能够自动应用复杂的上下文处理。
非上下文功能以相同的方式应用于字形,而不考虑相邻的字形。这些功能包括选择备用字形集以赋予文本不同的外观,以及用于数学排版或增强排版复杂性的字形替换。
例如,清单8-3中的代码激活了Helvetica Neue Medium字体定义的两个功能类型。
Listing 8-3 Activating Font Features
ini
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];
清单8-3中的代码激活了数字间距功能类型(由常量kNumberSpacingType表示),选择了等宽数字(kProportionalNumbersSelector),并激活了字符替代功能类型(kCharacterAlternativesType),其特征选择器标识符键值为2。本例中用于表示字体功能类型和选择器的常量在Core Text框架的SFNTLayoutTypes.h头文件中声明为枚举(CoreText/CoreText.h)。字符替代类型的情况下,没有预定义的常量来表示特征选择器标识符,因此您只需使用字体定义的数值。
由于字体功能由字体定义,确定支持的功能的最可靠方法是直接查询字体。您可以使用Core Text中的CTFontCopyFeatures函数来执行此操作,如清单8-4所示。
Listing 8-4 Querying Font Features
ini
UIFont *font = [UIFont fontWithName: @"HelveticaNeue-Medium" size: 12.0];
CFArrayRef fontFeatures = CTFontCopyFeatures((__bridge CTFontRef) font);
NSLog(@"properties = %@", fontFeatures);
清单8-5展示了由清单8-4中的CTFontCopyFeatures函数得到的字体特征数组,它显示在控制台日志中。
Listing 8-5 Typical Result from CTFontCopyFeatures Function
ini
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字体有两个字体功能:数字间距和字符替代。当您使用字体描述符激活字体功能并选择其设置时,此结果中最重要的值是功能类型标识符和功能选择器标识符。您将这些值添加到表示字体功能设置的字典数组中,并将该数组作为UIFontDescriptorFeatureSettingsAttribute的值传递,依次传递给fontDescriptorByAddingAttributes:或fontDescriptorWithFontAttributes:方法,如清单8-3所示。该清单中显示的常量的枚举值与CTFontCopyFeatures函数返回的功能类型标识符和功能选择器标识符的数值相关联。
如清单8-5所示,由CTFontCopyFeatures函数返回的字体特征数组还显示了功能类型是否是排他的,以及哪个功能选择器是默认的。当然,功能类型名称和功能选择器名称值提供了对可用字体功能及其设置的人类可读标识。
查询字体度量
当字体度量信息可用时,UIFont定义了许多方法用于访问字体的度量信息。诸如ascender、capHeight、xHeight等属性都与标准的字体度量信息相对应。图8-4显示了字体度量如何应用于字形尺寸,表8-2列出了与各种度量相关的属性名称。请参阅属性描述以获取更具体的信息。
Figure 8-4 Font metrics
Table 8-2 Font metrics and related UIFont
methods
Font metric | Properties |
---|---|
X-height | xHeight |
Ascent | ascender |
Cap height | capHeight |
Line height | lineHeight |
Descent | descender |
Point size | pointSize |
文本布局
由NSLayoutManager类实例化的布局管理器对象是Text Kit中文本显示的中心控制对象。布局管理器执行以下操作:
- 控制文本存储和文本容器对象(Controls text storage and text container objects)
- 从字符生成字形(Generates glyphs from characters)
- 计算字形位置并存储信息(Computes glyph locations and stores the information)
- 管理字形和字符的范围 (Manages ranges of glyphs and characters)
- 在视图请求时在文本视图中绘制字形 (Draws glyphs in text views when requested by the view)
- 计算文本行的边界框矩形 (Computes bounding box rectangles for lines of text)
- 控制连字符 (Controls hyphenation)
- 操作字符属性和字形属性 (Manipulates character attributes and glyph properties)t
在模型-视图-控制器范式中,布局管理器是控制器。NSTextStorage是NSMutableAttributedString的子类,它提供了模型的一部分,保存带有字体、样式、颜色和大小等属性的字符字符串。NSTextContainer也可以被认为是模型的一部分,因为它模拟了文本布局的页面几何布局。UITextView(或其他UIView对象)提供了显示文本的视图。NSLayoutManager作为文本系统的控制器,因为它将文本存储对象中的字符转换为字形,根据一个或多个文本容器对象的尺寸将它们排列成行,并协调一个或多个文本视图对象中的文本显示。
布局过程
布局管理器执行文本布局分为两个独立的步骤:字形生成和字形布局。布局管理器以懒惰的方式执行这两个布局步骤,即在需要的基础上进行。因此,一些NSLayoutManager方法会导致字形生成,而另一些方法则不会,字形布局也是如此。在生成字形并计算其布局位置后,布局管理器会缓存这些信息以提高后续调用的性能。
布局管理器缓存字形、属性和布局信息。它跟踪由于文本存储中字符的更改而被无效化的字形范围。有两种方式可以使字符范围自动无效:如果需要生成字形或者需要排列字形。如果您愿意,可以手动使字形或布局信息无效。当布局管理器收到需要了解无效范围内的字形或布局的消息时,它会生成字形或重新计算布局。
生成行片段矩形
布局管理器将文本放置在NSTextContainer对象中的字形行内。这些行在文本容器内的布局由其形状和包含的任何排除路径确定。无论行片段矩形在哪里与由排除路径定义的区域相交,这些部分的行都必须缩短或分段;如果整个区域有一个缺口,必须移动与之重叠的行以进行补偿。
布局管理器为给定的行建议一个矩形,然后要求文本容器调整矩形以适应它。建议的矩形通常跨越文本容器的边界矩形,但它可以更窄或更宽,也可以部分或完全位于边界矩形之外。布局管理器向文本容器发送的消息是lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:,它会根据文本布局的方向,返回矩形中可用的最大矩形。它还返回一个包含所有剩余空间的矩形,例如文本容器中空洞或间隙的另一侧的剩余空间。
在进行文本布局时,布局管理器会进行最后一次调整。这个调整是由文本容器固定的一小部分,称为行片段填充,它定义了行片段矩形两端留白的部分。文本在行片段矩形内的位置会根据这个值进行调整(矩形本身不受影响)。填充允许对文本容器边缘(以及任何空洞周围)的区域进行小规模的调整,并使文本不会直接与其他在该区域附近显示的图形相邻。您可以使用lineFragmentPadding属性更改填充的默认值。请注意,行片段填充不适合用于表达边距。对于文档边距,您应该设置UITextView对象在其包含视图中的位置和大小。对于文本边距,您应该设置文本视图的textContainerInset属性。此外,您可以使用NSMutableParagraphStyle的属性(如headIndent)设置单个段落的缩进值。
除了返回行片段矩形本身,布局管理器还返回一个称为已使用矩形的矩形。这是行片段矩形中实际包含字形或其他要绘制的标记的部分。按照惯例,两个矩形都包括行片段填充和行间距(根据字体的行高度量和段落的行间距参数计算)。但是,段落间距(前后)和文本周围添加的任何空间(例如由居中空格文本引起的空间)只包含在行片段矩形中,而不包含在已使用矩形中。
指定排除路径
文本容器维护一个表示接收器边界矩形内排除路径的UIBezierPath对象数组。当布局管理器向文本容器发送lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:消息,提议一个与排除路径定义的区域相交的行片段矩形时,文本容器返回一个调整后的行片段矩形,排除该区域。此过程如图8-6所示。
Figure 8-5 Line fragment fitting
指定多页和多列布局
在最简单的情况下,Text Kit对象是按单个配置的,即一个文本存储对象、一个文本容器和一个布局管理器,如图8-6所示。当您在Interface Builder中从对象库拖动一个文本视图时,会自动实例化此配置。UITextView对象提供其他对象并将它们连接在一起。您也可以在代码中创建此安排,如清单8-6所示。
Figure 8-6 Object configuration for a single text flow
您也可以在代码中创建此安排,如清单8-6所示。这段代码可以在一个视图控制器中,例如,一个名为UIViewController的子类中,该子类有一个名为textContainer的NSTextContainer属性。
Listing 8-6 Object creation for a single text flow
ini
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
现在有两个文本容器,而不是一个文本容器对应一个页面,每个文本容器控制文档的一部分。当文本被显示时,字形首先被放置在左上角的容器中。当该视图没有更多空间时,布局管理器会通知其代理它已经填满了容器。代理可以检查是否有更多的文本需要布局,并在必要时添加另一个文本容器。布局管理器继续在下一个容器中布局文本,在完成后通知代理,依此类推。同样,自定义视图(描绘为蓝色矩形)为这些文本列提供了画布。
您不仅可以拥有多个文本容器,还可以让多个NSLayoutManager对象访问相同的文本存储。图8-9说明了具有多个布局管理器的对象排列。这种安排的效果是提供同一文本的多个视图。如果用户在顶部视图中更改了文本,更改将立即反映在底部视图中(假设更改的位置在底部视图的边界内)。
Figure 8-9 Object configuration for multiple views of the same text
推荐阅读: 初识 TextKit