版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2018.09.01 |
前言
TextKit
框架是對Core Text
的封裝,用簡潔的調用方式實現(xiàn)了大部分Core Text
的功能。 TextKit是一個偏上層的開發(fā)框架,在iOS7
以上可用,使用它可以方便靈活處理復雜的文本布局,滿足開發(fā)中對文本布局的各種復雜需求。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框架詳細解析 (十一) —— 文本編程指南之輸入數(shù)據(jù)的自定義視圖(七)
12. TextKit框架詳細解析 (十二) —— 文本編程指南之展示和管理編輯菜單(八)
Using Text Kit to Draw and Manage Text - 使用TextKit繪制和管理文本
UIKit框架包括幾個類,其目的是在應用程序的用戶界面中顯示文本:UITextView
,UITextField
和UILabel
,如在Displaying Text Content in iOS中所述。從UITextView
類創(chuàng)建的文本視圖旨在顯示大量文本。底層UITextView是一個名為Text Kit
的強大布局引擎。如果您需要自定義布局過程或需要干預該行為,則可以使用Text Kit
。對于需要自定義解決方案的少量文本和特殊需求,您可以使用替代的底層技術,如 Lower Level Text-Handling Technologies中所述。
Text Kit
是UIKit框架中的一組類和協(xié)議,提供高質量的排版服務,使應用程序能夠存儲,布局和顯示具有精細排版的所有特征的文本,例如字距調整,連字,斷行和對齊。 Text Kit構建于Core Text
之上,因此它提供相同的速度和功能。 UITextView
與Text Kit
完全集成;它提供編輯和顯示功能,使用戶能夠輸入文本,指定格式屬性和查看結果。其他Text Kit
類提供文本存儲和布局功能。圖8-1顯示了Text Kit在其他iOS文本和圖形框架中的位置。
Text Kit
使您可以完全控制用戶界面元素中的文本呈現(xiàn)。 除了UITextView
之外,UITextField
和UILabel
都構建在Text Kit
之上,它與動畫,UICollectionView
和UITableView
無縫集成。 Text Kit采用完全可擴展的面向對象架構設計,支持子類化,代理和一組完整的通知,支持深度自定義。
Primary Text Kit Objects - 主要Text Kit對象
主要Text Kit
對象之間的數(shù)據(jù)流路徑如圖8-2所示。 Text view
是UITextView
類的實例,文本容器是NSTextContainer
類的實例,布局管理器是NSLayoutManager
類的實例,文本存儲是NSTextStorage
類的實例。 在Text Kit
中,NSTextStorage
對象存儲由UITextView
對象顯示的文本,并由NSLayoutManager
對象布置到由NSTextContainer
對象定義的區(qū)域中。
NSTextContainer
對象定義了可以布局文本的區(qū)域。通常,文本容器定義矩形區(qū)域,但通過創(chuàng)建NSTextContainer
的子類,您可以創(chuàng)建其他形狀:例如圓形,五邊形或不規(guī)則形狀。文本容器不僅描述了可以用文本填充的區(qū)域的輪廓,還維護了一個Bezier路徑陣列,這些路徑是其區(qū)域內沒有布局文本的禁區(qū)。在布局時,文本圍繞排除路徑流動,提供了包含圖形和其他非文本布局元素的方法。
NSTextStorage
定義了Text Kit擴展文本處理系統(tǒng)的基本存儲機制。 NSTextStorage
是NSMutableAttributedString
的子類,用于存儲由文本系統(tǒng)操縱的字符和屬性。它確保文本和屬性在編輯操作中保持一致的狀態(tài)。除了存儲文本之外,NSTextStorage
對象還管理一組客戶端NSLayoutManager
對象,通知它們對其字符或屬性的任何更改,以便它們可以根據(jù)需要中繼和重新顯示文本。
NSLayoutManager
對象編排其他文本處理對象的操作。它在將NSTextStorage
對象中的數(shù)據(jù)轉換為視圖顯示區(qū)域中的渲染文本的操作中進行干預。它將Unicode字符代碼映射到字形,并監(jiān)視由NSTextContainer
對象定義的區(qū)域內的字形布局。
注意:只要應用程序保證從單個線程訪問,就可以從子線程訪問
NLayoutManager
,NSTextStorage
和NSTextContainer
。
有關UITextView
的參考信息,請參閱UITextView Class Reference。 NSTextContainer
在NSTextContainer Class Reference for iOS
,NSLayoutManager
在NSLayoutManager Class Reference for iOS
和NSTextStorage
在NSTextStorage Class Reference for iOS
中描述。
Text Attributes - 文本屬性
Text Kit
處理三種文本屬性:字符屬性,段落屬性和文檔屬性(character attributes, paragraph attributes, and document attributes)
。 字符屬性包括諸如字體,顏色和下標之類的特征,這些特征可以與單個字符或一系列字符相關聯(lián)。 段落屬性是縮進,制表符和行間距等特征。 文檔屬性包括文檔范圍的特征,如紙張大小,邊距和視圖縮放百分比。
1. Character Attributes - 字符屬性
屬性字符串將字符屬性存儲為NSDictionary
對象中的鍵值對。 鍵是屬性名稱,由標識符(NSString常量)表示,例如NSFontAttributeName。 圖8-3顯示了一個屬性字符串,其中屬性字典應用于字符串中的范圍。
從概念上講,屬性字符串中的每個字符都有一個關聯(lián)的屬性字典。但是,通常,屬性字典適用于較長范圍的字符,即一行文本。 NSAttributedString
類提供了獲取字符索引并返回關聯(lián)屬性字典及其屬性值應用范圍的方法,例如attributesAtIndex:effectiveRange:。
除了使用預定義屬性外,您還可以將您選擇的任何屬性鍵值對分配給一個區(qū)間的字符。使用NSMutableAttributedString
方法addAttribute:value:range:將屬性添加到NSTextStorage
對象中的相應字符范圍。您還可以創(chuàng)建一個NSDictionary
對象,其中包含一組自定義屬性的名稱和值,并使用addAttributes:range:方法在一個步驟中將它們添加到字符范圍。要使用自定義屬性,需要NSLayoutManager
的自定義子類來使用它們。您的子類應重寫[drawGlyphsForGlyphRange:atPoint:](https://developer.apple.com/documentation/appkit/nslayoutmanager/1403158-drawglyphsforglyphrange)
方法。您的重寫可以先調用超類來繪制字形范圍,然后在頂部繪制自己的屬性。或者,您的重寫可以完全以您自己的方式繪制字形。
2. Paragraph Attributes - 段落屬性
段落屬性會影響布局管理器將文本行排列到頁面上的段落中的方式。 文本系統(tǒng)將段落屬性封裝在NSParagraphStyle
類的對象中。 其中一個預定義字符屬性NSParagraphStyleAttributeName的值指向包含該字符范圍的段落屬性的NSParagraphStyle
對象。 屬性修復確保只有一個NSParagraphStyle
對象與每個段落中的字符相關。
段落屬性包括諸如對齊,制表位,換行模式和行間距(也稱為前導)等特征。
3. Document Attributes - 文檔屬性
文檔屬性與文檔整體相關。 文檔屬性包括紙張大小,邊距和視圖縮放百分比等特征。 盡管文本系統(tǒng)沒有用于存儲文檔屬性的內置機制,但NSAttributedString
初始化方法(如initWithRTF:documentAttributes:)可以填充您提供的NSDictionary
對象,其中包含從RTF
或HTML
數(shù)據(jù)流派生的文檔屬性。 相反,如果傳遞對包含消息的NSDictionary對象的引用,則編寫RTF數(shù)據(jù)的方法(如RTFFromRange:documentAttributes:)會寫入文檔屬性。
4. Attribute Fixing
編輯屬性字符串可能會導致必須通過attribute fixing
來清除不一致性。 NSMutableAttributedString
的UIKit擴展定義了fixAttributesInRange:方法來修復附件,字符和段落屬性之間的不一致。 這些方法可確保在刪除附件字符后不會保留附件,字符屬性僅適用于該字體中可用的字符,并且段落屬性在各段落中都是一致的。
Changing Text Storage Programmatically - 以編程方式更改文本存儲
NSTextStorage
對象充當Text Kit的字符數(shù)據(jù)存儲庫。此數(shù)據(jù)的格式是屬性字符串attributed string
,它是一系列字符(Unicode編碼)和相關屬性(如字體,顏色和段落樣式)。表示屬性字符串的類是NSAttributedString
和NSMutableAttributedString
,其中NSTextStorage
是子類。如Character Attributes中所述,文本塊中的每個字符都有一個鍵和與之關聯(lián)的值的字典。鍵命名屬性(例如NSFontAttributeName),關聯(lián)值指定該屬性的特征(例如Helvetica 12-point
)。
以編程方式編輯文本存儲對象有三個階段。第一階段是向它發(fā)送beginEditing消息以宣布一組更改。
在第二階段,您向它發(fā)送一些編輯消息,例如replaceCharactersInRange:withString:和setAttributes:range:,以實現(xiàn)字符或屬性的更改。每次發(fā)送這樣的消息時,文本存儲對象都會調用edited:range:changeInLength:跟蹤自收到beginEditing消息以來受影響的字符范圍。
在第三階段,當您完成文本存儲對象的更改后,將向其發(fā)送endEditing消息。這使它發(fā)出代理消息textStorage:willProcessEditing:range:changeInLength:并調用自己的processEditing方法,修復記錄的已更改字符范圍內的屬性。有關屬性修復的信息,請參見Attribute Fixing。
在修復其屬性之后,文本存儲對象發(fā)送代理方textStorage:didProcessEditing:range:changeInLength:,為委托者提供驗證并可能更改屬性的機會。 (盡管代理可以在此方法中更改文本存儲對象的字符屬性,但它不能在不使文本存儲處于不一致狀態(tài)的情況下更改字符本身。)最后,文本存儲對象發(fā)送processEditingForTextStorage:edited:range:changeInLength:invalidatedRange:消息到每個關聯(lián)的布局管理器 - 指示文本存儲對象中已更改的范圍,以及這些更改的性質。布局管理器反過來使用此信息重新計算其字形位置,并在必要時重新顯示。
Working with Font Objects - 使用字體對象
計算機字體是OpenType
或TrueType
等格式的數(shù)據(jù)文件,包含描述一組字形的信息,如 Characters and Glyphs中所述,以及字形渲染中使用的各種補充信息。 UIFont
類提供了獲取和設置字體信息的接口。 UIFont實例提供對字體特征和字形的訪問。 Text Kit將字符信息與字體信息組合在一起,以選擇文本布局期間使用的字形。您可以通過將字體對象傳遞給接受它們作為參數(shù)的方法來使用它們。字體對象是不可變的,因此可以安全地從應用程序中的多個線程中使用它們。
您不使用alloc
和init方
法創(chuàng)建UIFont對象;相反,您使用preferredFontForTextStyle:文本樣式常量或fontWithName:size:。您還可以使用字體描述符使用fontWithDescriptor:size:來創(chuàng)建字體。這些方法檢查具有指定特征的現(xiàn)有字體對象,如果有,則返回它。否則,他們會查找請求的字體數(shù)據(jù)并創(chuàng)建相應的字體對象。
1. Text Styles - 文本樣式
iOS 7中引入的文本樣式是字體的預期用途的語義描述,并通過稱為動態(tài)類型(Dynamic Type)
的機制實現(xiàn)。 文本樣式按使用組織,并由UIFontDescriptor.h
中定義的常量表示,如表8-1所示。 用于由文本樣式描述的目的的實際字體可以基于許多動態(tài)考慮因素而變化,包括用戶的內容大小類別首選項,其由UIApplication
屬性preferredContentSizeCategory表示。 要獲取給定文本樣式的字體對象,請將相應的常量傳遞給UIFont
方法preferredFontForTextStyle:。 要獲取文本樣式的字體描述符,請將常量傳遞給UIFontDescriptor
方法preferredFontDescriptorWithTextStyle:。 (有關字體描述符的更多信息,請參閱Using Font Descriptors。)
文本樣式通過動態(tài)類型機制為應用程序帶來許多優(yōu)勢,所有這些都增強了文本的可讀性。動態(tài)類型以協(xié)調的方式響應用戶首選項,并響應可訪問性設置以增強易讀性和超大類型。也就是說,當您調用preferredFontForTextStyle:時,返回的特定字體包括根據(jù)用戶首選項和上下文而變化的特征,包括跟蹤(字母間距)調整,以及針對特定文本樣式常量指定的用途進行調整。
使用文本樣式常量返回的字體旨在用于應用程序中除用戶界面元素中的文本之外的所有文本,例如buttons, bars, and labels
。當然,您需要選擇在您的應用中看起來正確的文字樣式。觀察UIContentSizeCategoryDidChangeNotification也很重要,這樣您就可以在用戶更改內容大小類別時重新布局文本。當您的應用收到該通知時,它應該將invalidateIntrinsicContentSize消息發(fā)送到由Auto Layout
定位的視圖,或者將setNeedsLayout發(fā)送到手動定位的用戶界面元素。它應該使首選字體或字體描述符無效并根據(jù)需要獲取新字體。
2. Using Font Descriptors - 使用字體描述符
從UIFontDescriptor
類實例化的字體描述符提供了一種使用屬性字典描述字體的方法,并用于創(chuàng)建UIFont
對象。特別是,您可以從字體描述符創(chuàng)建UIFont對象,您可以從UIFont對象獲取描述符,并且您可以更改描述符并使用它來創(chuàng)建新的字體對象。您還可以使用字體描述符來指定應用程序提供的自定義字體。
字體描述符可以存檔,這是使用文本樣式的一個優(yōu)點。您不應該緩存由文本樣式指定的字體對象,因為它們是動態(tài)的 - 它們的特性會根據(jù)用戶首選項隨時間變化。但是您可以緩存字體描述符以保留字體的描述,然后在以后取消存檔并使用它來創(chuàng)建具有相同特征的字體對象。
您可以使用字體描述符在系統(tǒng)中查詢與特定屬性匹配的可用字體,然后創(chuàng)建與這些屬性匹配的字體實例,例如名稱,特征,語言和其他功能。例如,您可以使用字體描述符來檢索與給定字體系列名稱匹配的所有字體,使用CSS標準定義的系列名稱,如Listing 8-1
所示。
// Listing 8-1 Font family name matching
UIFontDescriptor *helveticaNeueFamily =
[UIFontDescriptor fontDescriptorWithFontAttributes:
@{ UIFontDescriptorFamilyAttribute: @"Helvetica Neue" }];
NSArray *matches =
[helveticaNeueFamily matchingFontDescriptorsWithMandatoryKeys: nil];
如上所示,matchingFontDescriptorsWithMandatoryKeys:方法返回系統(tǒng)中所有Helvetica Neue
字體的字體描述符數(shù)組,例如HelveticaNeue
,HelveticaNeue-Medium
,HelveticaNeue-Light
,HelveticaNeue-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屬性。 當然,這種行為是必需的,因為字體大小由動態(tài)類型機制決定。
3. Activating Font Features - 激活字體特征
字體描述符的另一個重要用途是激活和選擇字體特征。字體特征是字體的排版屬性,當字體由文本系統(tǒng)呈現(xiàn)時控制其外觀的各個方面。僅當字體設計者選擇包含字體時,字體特征才可用于字體。某些字體特征以少數(shù)字體提供,而其他字體則適用于許多字體。此外,安裝在不同平臺上的相同字體的不同版本可以在其可用字體特征方面有所不同。
字體特征分為稱為feature types
的類別,其中單個特征選擇器選擇特定特征設置。特征類型可以是獨占的,也可以是非獨占的。如果特征類型是獨占的,則您一次只能選擇一個可用的特征選擇器,例如數(shù)字是比例還是固定寬度。如果特征類型為非排他性,則可以一次啟用任意數(shù)量的特征選擇器。例如,對于連字特征類型,您可以選擇字體支持的可用連字類的任意組合。
注意:如果選擇字體中不可用的特征,則不會看到字體字形外觀發(fā)生變化。
一些特征是上下文的,而另一些特征是非上下文的。將上下文特征應用于字形的方式取決于字形與相鄰字形相比的位置。文本系統(tǒng)布局功能的一個強大方面是它能夠自動應用復雜的上下文處理。
無論相鄰的字形如何,非上下文特征(Noncontextual features )
都以相同的方式應用于字形。這些特征包括選擇替代字形集,以便為文本提供不同的外觀和字形替換,以達到數(shù)學排版或增強排版復雜性的目的。
例如,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中的代碼激活數(shù)字間距特征類型(由常量kNumberSpacingType
表示),選擇比例寬度數(shù)字(kProportionalNumbersSelector)
和字符替代特征類型(kCharacterAlternativesType)
,其中特征選擇器標識符鍵值為2。 此示例中用于表示字體特征類型和選擇器的常量在Core Text
框架(CoreText / CoreText.h)
中的頭文件SFNTLayoutTypes.h
中聲明為枚舉。 對于字符替換類型,沒有預定義常量來表示特征選擇器標識符,因此您只需使用字體定義的數(shù)值。
由于字體功能是由字體定義的,因此確定支持特征的最可靠方法是直接查詢字體。 您可以使用Core Text中的CTFontCopyFeatures函數(shù)執(zhí)行此操作,如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-4
中CTFontCopyFeatures
函數(shù)產(chǎn)生的字體特征數(shù)組,因為它顯示在控制臺日志中。
// 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 Spacing
和Character Alternatives
。使用字體描述符激活字體特征并在其設置中進行選擇時,此結果中最重要的值是特征類型標識符和特征選擇器標識符。將這些值添加到表示字體特征設置的字典數(shù)組中,然后將該數(shù)組用作UIFontDescriptorFeatureSettingsAttribute的值,并依次傳遞給fontDescriptorByAddingAttributes:或fontDescriptorWithFontAttributes:方法,如Listing 8-3
所示。該列表中顯示的常量的枚舉值與CTFontCopyFeatures
函數(shù)返回的特征類型標識符和特征選擇器標識符的數(shù)值相關聯(lián)。
如Listing 8-5所示,CTFontCopyFeatures
函數(shù)生??成的字體特征數(shù)組還顯示特征類型是否為獨占,以及哪個特征selector
是默認值。當然,功能類型名稱和功能selector
名稱值為可用的字體特征及其設置提供了人類可讀的標識。
4. Querying Font Metrics - 查詢字體度量標準
當該信息可用時,UIFont
定義了許多用于訪問字體度量信息的方法。 諸如ascender, capHeight, xHeight等屬性都對應于標準字體度量信息。 圖8-4顯示了字體度量如何應用于字形維度,表8-2列出了與各種度量相關的屬性名稱。 有關更多具體信息,請參閱屬性說明。
Laying Out Text - 布局文本
從NSLayoutManager
類實例化的布局管理器對象是Text Kit中文本顯示的中央控制對象。布局管理器執(zhí)行以下操作:
- 控制文本存儲和文本容器對象
- 從字符生成字形
- 計算字形位置并存儲信息
- 管理字形和字符的范圍
- 視圖請求時在文本視圖中繪制字形
- 計算文本行的邊界框矩形
- 控制連字符
- 處理字符屬性和字形屬性
在模型 - 視圖 - 控制器范例中,布局管理器是控制器。 NSTextStorage
是NSMutableAttributedString
的子類,它提供了模型的一部分,包含一串文本字符,其中包含字體,樣式,顏色和大小等屬性。 NSTextContainer
也可以被視為模型的一部分,因為它模擬了布局文本的頁面的幾何布局。 UITextView
(或其他UIView對象)提供顯示文本的視圖。 NSLayoutManager
用作文本系統(tǒng)的控制器,因為它將文本存儲對象中的字符轉換為字形,根據(jù)一個或多個文本容器對象的尺寸將它們排成行,并在一個或多個文本視圖對象中協(xié)調文本顯示。
1. The Layout Process - 布局過程
布局管理器在兩個單獨的步驟中執(zhí)行文本布局:字形生成和字形布局。布局管理器懶地執(zhí)行兩個布局步驟,即根據(jù)需要執(zhí)行。因此,一些NSLayoutManager
方法會導致字形生成,而其他方法則不會,字形布局也是如此。在生成字形并在計算其布局位置之后,布局管理器會緩存信息以提高后續(xù)調用的性能。
布局管理器緩存字形,屬性和布局信息。它跟蹤文本存儲中字符更改失效的字形范圍。有兩種方法可以自動使字符范圍無效:如果需要生成字形或者需要字符布局。如果您愿意,可以手動使字形或布局信息無效。當布局管理器收到需要知道字形或布局在無效范圍內的消息時,它會生成字形或根據(jù)需要重新計算布局。
2. Generating Line Fragment Rectangles - 生成線段矩形
布局管理器在字形行中的NSTextContainer
對象中布局文本。文本容器中這些行的布局由其形狀和它包含的任何排除路徑確定。無論線段矩形與由排除路徑定義的區(qū)域相交,這些部分中的線都必須縮短或分段;如果整個區(qū)域之間存在間隙,則必須移動與其重疊的線以進行補償。
布局管理器為給定的行提出一個矩形,然后要求文本容器調整矩形以適應。建議的矩形通常跨越文本容器的邊界矩形,但它可以更窄或更寬,并且它也可以部分或完全位于邊界矩形之外。布局管理器發(fā)送文本容器以調整建議的矩形的消息是lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:,它根據(jù)文本的布局方向返回可用于建議矩形的最大矩形。它還返回一個包含任何剩余空間的矩形,例如文本容器中孔或間隙另一側留下的空間。
布局管理器在實際將文本插入矩形時進行最后一次調整。這個調整是由文本容器固定的一小部分,稱為行片段填充(line fragment padding)
,它將行片段矩形每端的部分定義為空白。文本在行片段矩形內插入此量(矩形本身不受影響)。填充允許在邊緣(以及任何孔周圍)對文本容器區(qū)域進行小規(guī)模調整,并使文本不直接鄰接區(qū)域附近顯示的任何其他圖形。您可以使用lineFragmentPadding屬性更改默認值的填充。請注意,線段填充不是表達邊距的合適方法。對于文檔邊距,應在其封閉視圖中設置UITextView
對象的位置和大小。對于文本邊距,您應該設置文本視圖的textContainerInset
屬性。此外,您可以使用NSMutableParagraphStyle
屬性(如headIndent)為各個段落設置縮進值。
除了返回線段矩形本身之外,布局管理器還返回一個稱為使用矩形(used rectangle)
的矩形。這是線段矩形的一部分,實際上包含要繪制的字形或其他標記。按照慣例,兩個矩形都包括行片段填充和行間距(根據(jù)字體的行高度度量和段落的行間距參數(shù)計算)。但是,段落間距(前后)和文本周圍添加的任何空格(例如由中心間距文本引起的空間)僅包含在線段矩形中,并且不包含在使用的矩形中。
3. Specifying Exclusion Paths - 指定排除路徑
文本容器維護一個UIBezierPath
對象數(shù)組,表示接收器邊界矩形內的排除路徑。 當布局管理器向文本容器發(fā)送lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:消息,建議與排除路徑定義的某個區(qū)域相交的線段矩形時,文本容器返回一個不包括該區(qū)域的調整后的線段矩形。 該過程如圖8-6所示。
4. Specifying Multipage and Multicolumn Layouts - 指定多頁和多列布局
在最簡單的情況下,Text Kit
對象是單獨配置的,即一個文本存儲對象,一個文本容器和一個布局管理器,如圖8-6所示。 從Interface Builder中的對象庫拖動文本視圖時,將自動實例化此配置。 UITextView
對象會檢出其他對象并將它們連接在一起。 您還可以在代碼中創(chuàng)建此排列,如Listing 8-6
所示。
您還可以在代碼中創(chuàng)建此排列,如Listing 8-6
所示。 此代碼可以位于視圖控制器中,例如,UIViewController
的子類,它具有名為textContainer
的NSTextContainer
屬性。
// 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];
僅具有一個文本容器和一個文本視圖限制了此配置。 在這種安排中,文本在由文本容器定義的區(qū)域內不間斷地流動。 這種安排無法滿足分頁符,多列布局和更復雜的布局。
通過使用多個文本容器,每個容器具有關聯(lián)的文本視圖,可以進行更復雜的布局安排。 例如,要支持分頁符,應用程序可以配置文本對象,如圖8-7所示。
每個文本容器對應于文檔的頁面。 顯示文本的視圖可以嵌入到應用程序提供的自定義視圖對象中,作為文本視圖的背景。 反過來,此自定義視圖可以嵌入到UIScrollView
對象中,以使用戶能夠滾動文檔的頁面。
多列文檔可以使用類似的對象排列建模,如圖8-8所示。
現(xiàn)在有兩個文本容器 - 一個用于頁面上的每一列,而不是讓一個文本容器對應一個頁面。每個文本容器控制文檔的一部分。顯示文本時,首先在左上角的容器中布置字形。當該視圖中沒有空間時,布局管理器通知其代理它已完成填充容器。代理可以檢查是否有更多需要布局的文本,并在必要時添加另一個文本容器。布局管理器繼續(xù)在下一個容器中布置文本,在完成時通知代理,依此類推。同樣,自定義視圖(描繪為藍色矩形)為這些文本列提供了畫布。
您不僅可以擁有多個文本容器,還可以擁有多個訪問同一文本存儲的NSLayoutManager
對象。圖8-9說明了具有多個布局管理器的對象布局。這種安排的效果是提供相同文本的多個視圖。如果用戶更改頂視圖中的文本,則更改會立即反映在底部視圖中(假設更改的位置在底部視圖的邊界內)。
后記
本篇主要講述了使用TextKit繪制和管理文本,感興趣的給個贊或者關注~~~