內容來自于 iOS文檔中 About Text Handling in iOS 部分
ios平臺提供了顯示及編輯文本,及顯示格式化的文本和web內容的功能,提供的接口類包括上至text view,text field和web view,下至layout manager,供繪制,控制文本的布局,以及管理文本。
UIKit框架中的類足以實現編輯菜單項的自定義,自定義input view,及文本在app內及app之間的復制,剪切及粘貼。
文本處理概覽
普通文本,用UITextField,UITextView,UILabel即可,Web文本用UIWebView
編輯文本時需要和keyboard交互,并在keyboard消失時通過delegate記住輸入的文本
最強大的功能即是直接繪制和管理文本
text view底層是強大的稱為Text Kit的layout引擎,如果想自定義layout過程,或者參與這個過程,可以使用Text Kit(using text kit to draw and manage text),它由多個類和protocol所組成的,支持對文本進行存儲,顯示及使用精良的kerning,連體字符,justification等特性進行排版 這些功能。
對于大多app來說,UIKit和TextKit就已經夠用了,對于少部分特殊需求來說,可以使用更底層的技術,比如Core Text,Core Graphics和Core animation,當然還有UiKit本身。
自定義數據的輸入和編輯
自定義input view可以取代系統keyboard以進行特殊數據的輸入,使用UIPasteboard及相關類,app可以在app內或者 app之間進行復制,剪切數據,當然還可以自定義復制-剪切-粘貼 這個菜單
管理text fields和text view
text field和text view的功能在于顯示文本及開啟了編輯文本的入口,其承擔的任務在于配置文本對象,訪問當前的文本,驗證用戶的輸入,以及顯示在textfield中放置的view,比如書簽按鈕。完成這些任務是通過delegate,即UITextFieldDelegate及UITextViewDelegate來完成的。
發送給delegate的一系列message
UITextField和UITextView類的實例通常會在處于first-responder狀態的特定文本對象發生或即將發生變化的時候向其delegate發送一系列名字類似的消息。當用戶點擊一個text object的時候,它會成為first responder,同時系統會顯示keyboard并為這個text object開始editing session。而若用戶點擊了另一個text object或者點擊了結束編輯的按鈕,則當前first-responder的text object會重新分配first-responder狀態。若沒有選中其他text object則keyboard收起,否則為選中的另一個text object顯示其keyboard。
上面所述是一般特征,當然也是有例外的:
1 在iPad中,對于使用"form sheet"style來模態顯示的其view的view controller,則keyboard一旦顯示,則不會消失,除非用戶點擊dismiss key 或者modal view controller被dismiss掉。其目的是為了避免用戶在多個text field移動時過多的keyboard動畫。
2 自定義input view時,input view是text view或者自定義view的用來替換系統keyboard的屬性值。有input views的時,text object的keyboard可以在其仍然是first-responder的時候被UIKit交換出去,或者也可以為非text object也可以顯示一個類似于keyboard的input view。
發送給text view的delegate的消息序列如下:
1 在text object成為first-responder之前 textfield/textview shouldbeginediting:,可以在此方法內返回YES/NO 以確認是否愿意text object成為first responder
2 在text object成為first-responder之后,textfield/textview DidBeginEditing:
3 在editing session中:用戶輸入及編輯文本時,text object會調用特定的delegate方法,比如文本變化時的textViewDidChange:,或者text field的delegate會在用戶點擊了clear button之后收到textFieldShouldClear:獲取是否應當清空文件的信息。
4 textobject即將resign first-responder的時候,會向delegate發送textField/textView ShouldEndEditing: ,實現這個方法的初衷其實是為了校驗用戶輸入是否合法,比如如果輸入文本需要遵守一定的格式,則此方法可以返回NO
textfield另一個相關的方法是textFieldShouldReturn:,用戶點擊return鍵的時候會詢問delegate是否resign first-responder。
5 在已經resign了first-responder之后textField/textView DidEndEdting:。
其它希望知道text views變化情況的對象可以通過監聽它們的notification。
配置text fields和text views
配置文本特性:文本顏色,對齊,font族,font typeface和font size
配置keyboard:keyboard type,返回鍵名,安全文本輸入,auto-enabled 返回鍵,這些都是UITextInputTrait所聲明的特性(需要注意的是對于text view的情況,auto-enabled 返回鍵是作為回車換行鍵的)
配置特定于Text-field:border,背景圖,disabled image,清空按鈕和占位字符。同時還可以設置UIControl的信息
配置特定于text view:可編輯狀態,數據檢測(比如電話號碼和URL鏈接),同時還有UIScrollView的屬性
多text field及text view情況下區分
對于delegate方法調用的時候,區分是哪個text view發出來的delegate消息,可以使用outlet和tag兩種方法:outlet即是連接interface builder中的控件,并在delegate方法中做判斷;tag方法即為各控件定義tag,并根據不同的tag做區分
獲取輸入的文本和設置的文本
使用textview的text屬性進行讀取和設置
對text field使用Formatters
在輸入日期的時候除了使用datepicker之外,也可以使用text view加nsdateformatter的方法,為了確保dateformatter拿到正常的字符串來解析,需要驗證輸入的文本
驗證輸入文本
有時候不能直接接受用戶輸入到text view中的文本,最好的驗證時機是textField/textView ShouldEndEditing:
另一個時機是 textFiled向delegate發送textField:shouldChangeCharactersInRange:replacementString:消息時。
在textField中使用覆蓋view
覆蓋view是放置在textField左邊或者右邊角落的小塊view,它們通常是作為控件存在,并作用于當前textField的內容。搜索和書簽是覆蓋view的兩項特殊任務,當然也肯定有其他情況。比如如下的覆蓋view會使用text field中的URL加載一個web browser。
創建overlay view需要創建一個能夠適配textField高度的view,并設定一張尺寸合適的image。如果view是button或者control,可以使用target,action,及triggering control event來呼應用戶操作。一般情況下的需求是text field是編輯焦點時,顯示overlay view,這時只需要在delegate的textFieldDidBeginEditing:中將其賦值給leftView或者rightView即可。可以控制overlay view在編輯session中出現的時機,比如用戶開始輸入之前或者用戶輸入之后(為leftViewMode或者rightViewMode屬性賦值一個UITextFieldViewMode常數)。而要移除overlay view也很簡單,只需要在textFieldDidEndEditing:中將leftView和rightView賦nil即可。
跟蹤TextView中選中的文本
textViewDidChangeSelection:方法可以使得追蹤用戶對選中文本的修改成為可能,并獲取選中的子串并做相關的操作,比如將所有字符變成大寫等。
顯示web內容
如果使用了UIWebView對象,那么可以顯示本地或者網絡上的內容。
顯示本地內容
loadData:MIMEType:textEncodingName:baseURL: 或者 loadHTMLString:baseURL:方法,如可以這樣顯示pdf文件
NSData *pdfData = [NSData dataWithContentsOfFile:thePath];
[(UIWebView *)self.view loadData:pdfData MIMEType:@"application/pdf"
textEncodingName:@"utf-8" baseURL:nil];
雖然textEncodingName對pdf數據沒有影響,但仍在上述代碼中保留。
顯示網絡內容
加載遠程網頁需要使用loadRequest:(NSURLRequest*)req,由于加載網絡內容可能費時,可能需要顯示一個活動顯示器以表示加載正在進行,可能通過添加遵守UIWebViewDelegate的delegate來實現這個目標。
如果在初始化基于網絡的請求之后,需要釋放UIWebView,則必須在釋放WebView之前取消掉在處理中的request,可以使用webview的stopLoading方法取消一個加載請求。比較典型的地方是在viewController的viewWillDisappear方法中。判定request是否仍然pending可以通過web view的loading屬性。
管理鍵盤
用戶點擊textField,textView或者web view的某個域的時候,需要出現鍵盤,這時候可以配置鍵盤的各種特性,需要在編輯開始及結束的時候控制鍵盤,而且由于鍵盤出現時會擋住屏幕的一部分,所以還需要對界面進行相應的調整。
鍵盤和輸入法
先略過textfield和textView的keyboard配置,說下webview的鍵盤配置,雖然UIWebView不直接支持UITextInputTraits協議,但可以指定HTML中input元素的屬性,比如可以設定autocorrect 和 autocapitalize以設定鍵盤的行為
也可以指定希望使用的keyboard類型,比如使用tel,email,url為type屬性值以使用對應的鍵盤,或者為pattern指定"[0-9]*" 或 "\d*"值以使用數字鍵盤,這些HTML5特性在iOS上都是支持的。
管理鍵盤
雖然UIKit對象通常直接響應用戶操作并顯示鍵盤,但仍然可以管理鍵盤,如下部分描述keyboard的管理。
接收鍵盤通知
UIKeyboardWillShowNotification
UIKeyboardDidShowNotification
UIKeyboardWillHideNotification
UIKeyboardDidHideNotification
使用userInfo中的信息即可獲取鍵盤尺寸信息,使用UIKeyboardFrameBeginUserInfoKey和 UIKeyboardFrameEndUserInfoKey
在解決鍵盤遮蓋界面內容的時候,比如UIScrollView的情況,則在鍵盤出現之后,使用scrollRectToVisible:animated:方法將點擊的內容顯示進視角中。
另一種滾動編輯區域的辦法是將scrollview的frame高度向屏幕底部增加鍵盤的高度,并將offset設置為activefield的y 減去鍵盤高度
復制剪切和粘貼操作
用戶可以在應用內或者應用間進行文本,圖片和其他數據的復制,剪切和粘貼,UIKit在UITextView,UITextField和UIWebView中實現了復制-剪切-粘貼,如果想使用這些特性,可以使用這些類,或者自己實現這些特性。這部分接下來的部分描述實現這些操作的接口。
UIKit中的復制粘貼操作
UIPasteboard類提供pasteboard:app內或者app間共享數據的保護區域,這個類提供了從讀取和向寫入 數據到pasteboard的方法。
UIMenuController提供在復制,剪切和粘貼的選擇塊之上或之下顯示的編輯菜單。菜單中默認的命令有復制,剪切,粘貼,選擇和全選,可以添加自定義的菜單項。
UIResponder提供了canPerformAction:withSender:方法來根據當前上下文來顯示或者移除菜單中的命令
UIResponderStandardEditActions這個協議聲明了處理復制,剪切,粘貼,選擇和全選的接口,當用戶點擊命令時,相應的UIResponderStandardEditActions方法會被調用。
剪貼板
剪貼板是在app內及app之間交換數據的標準機制,剪貼板最常用的是處理復制,剪切和粘貼:
1 用戶在app內選擇數據并選擇copy或者cut菜單命令時,選擇的數據會放置到剪貼板上
2 當用戶選擇paste菜單命令時,數據從剪貼板復制到當前app中
iOS中剪貼板也支持find操作,此外,還可以在app之間通過剪貼板使用自定義的URL范式而非復制剪切粘貼來傳輸數據,可以查看Updating Your Info.plist Settings 部分以查看這個技巧的信息。
撇開操作,使用剪貼板對象的基本操作是寫入和讀取數據,雖然這些操作概念上比較簡單,但這份簡單實際上掩蓋了很多細節。主要的難點在于存在很多種表示數據的方式,而這份復雜導致需要對效率進行考量,接下來會討論這些以及其它細節。
命名剪貼板
剪貼板可以是公有,也可以是私有的,公有剪貼板叫做系統剪貼板,私有剪貼板是app自己創建的剪貼板,剪貼板必須有獨一無二的名字,UIPasteboard定義了兩個系統剪貼板:
UIPasteboardNameGeneral 是用來對一系列類型的數據進行復制剪切粘貼操作的剪貼板,可以通過generalPasteboard這個類方法來獲取其單例
UIPasteboardNameFind是用于搜索的,當前輸入searchable(UISearchBar)的字符串會被寫入其中,因此可以在app間共享,可以使用參數UIPasteboardNameFind調用類方法pasteboardWithName:create:來獲取這個剪貼板
通常情況下,使用系統剪貼板就已經夠用了,但必要情況下可以自己創建剪貼板,通過pasteboardWithName:create:方法,也可以通過pasteboardWithUniqueName獲取一個獨一無二的app內剪貼板。
剪貼板持久化
系統剪貼板是持久化的,app剪貼板也可以是持久化的,非持久化的app剪貼板在app退出后就被移除,而持久化的剪貼板可以跨越app生存期和系統重啟而生存下來,可以通過persistent屬性設為YES來實現剪貼板的持久化。
剪貼板屬主和items
上一次輸入數據到剪貼板的對象稱為剪貼板的owner,每塊寫進剪貼板的數據都稱為一個item,剪貼板可以包含單條或者多條item,app可以放置任意多條item。比如若一段選擇包含文字和圖片,則剪貼板會讓你將文本和圖片分成多條item復制進去,而app從剪貼板中讀取多條item時,可以選擇只讀取自己支持的item,比如只讀取文本。
剪貼板UTI
剪貼板操作經常在多個app之間進行,各app不需要知道其他app的存在,也不需要知道其他app能夠處理的數據類型。為了最大化共享的潛力,剪貼板可以持有同一個剪貼板item的多種表示形式,比如一個富文本編輯器可能提供HTML,PDF及純文本形式的所復制的數據。剪貼板上的item包含所有app所能提供的此item數據的所有形式。
剪貼板item的每種形式都是用一個唯一類型標識符(Unique Type Identifier (UTI))所表示的(UTI只是一個唯一標識特定數據類型的字符串),UTI提供了一種識別數據的能用方法。如果想支持自定義類型,可以使用反DNS記號規則,比如com.jeff.pastetype,具體可參考Uniform Type Identifiers Overview。
比如,假定app支持選擇富文本和圖片,其可能會在剪貼板上放置富文本版及unicode版本的選中的文本和多種形式的選中的圖片。每個item的每種形式的數據都與item存儲在一起的,如下圖:
一般為了盡可能地共享,剪貼板item需要包含盡可能多的數據形式,剪貼板讀取數據的時候盡量選所支持的最豐富的類型。
change count
change count是與pasteboard一對一個一個數據,用來記錄剪貼板內容每次添加,修改及移除時,數量增1 ,每次changecount增加時,pasteboard都會發通知出來通知監聽的觀察者。
復制粘貼的第一步
在復制剪切粘貼任何數據之前,需要先選擇,選擇某item(并顯式地通過UI也好,什么也好指示了此選中)之后,需要顯示編輯菜單,用來指示此次選擇,系統編輯菜單包含的選項有Copy, Cut, Paste, Select, 和 Select All,選中某菜單項時,相應的UIResponderStandardEditActions方法實現(比如cut: 或者 paste:)會被調用。詳情參見Managing the Selection and the Edit Menu
復制剪切所選
用戶點擊菜單項時,會觸發responder object的響應,比如cut:,copy:等,通常是first responder,如果其未實現,則會沿著responder chain傳遞下去,UIResponderStandardEditActions這個非正式協議實現了這些方法,由于它是個非正式的協議,為了更好地利用系統對responder chain的遍歷,可以對繼承自UIResponder的對象實現此協議并將其安裝到responder chain中。
針對cut:和copy:,需要將所選中的內容按盡可能多種形式的數據和對象寫進剪貼板中。這個操作會包含如下這些步驟(假定只有一個item):
1 根據選擇的內容,獲取此對象對應的對象或二進制數據
二進制數據必須封裝在NSData中,如果還想寫另一個類型的對象進剪貼板,則其必須是一個property list對象,即其必須是NSString, NSArray, NSDictionary, NSDate, NSNumber, 或 NSURL 這些對象中的一個
2 如果可能的話為對象生成一種或多種其他形式的數據
比如,如果為選中的圖片創建了UIImage對象,則可以使用UIImageJPEGRepresentation 和 UIImagePNGRepresentation將其轉換成不同的形式。
3 獲取pasteboard對象
4 為寫進剪貼板的item中的每種形式的數據選擇一種合適的UTI
5 將每種形式的數據寫進第一個剪貼板item中
寫數據對象用 setData:forPasteboardType:
寫property list對象用setValue:forPasteboardType:
6 如果命令是cut:,則將其從你的數據模型中移除并更新view
粘貼
當用戶點擊粘貼時,在responder chain中調用paste: ,其從剪貼板中讀取你的app支持一種形式的數據,步驟如下(假定只有一個item)
1 獲取pasteboard對象
2 使用containsPasteboardTypes:方法或者檢查pasteboardTypes:返回數據以驗證是否剪貼板第一個item中是否有當前app可處理的數據
3 如果有可處理的數據,則使用dataForPasteboardType:讀取封裝的NSData對象,或者使用valueForPasteboardType:讀取property list對象
4 將數據添加進你的數據模型中
結束操作
當從各命令的處理中返回時,菜單會消失,當然也可以手動將其繼續顯示,可以參見Dismissing the Edit Menu
自定義數據輸入view
主要是inputview及input accessory view的定制,及輸入點擊聲音等的處理,先略過
顯示及管理編輯菜單
管理選擇和編輯菜單
選擇可以是一塊文本,一張圖片,一個URL,一種顏色或任何其他形式的數據,包括自定義對象,你必須對你view中的對象的選擇進行管理。如果你要自定義選擇的手勢,或者支持多段選擇,都需要自己實現這種“選擇”。
當你的app斷定需要出現菜單時(可能是要做選擇),可能通過這些步驟來完成:
1 調用UIMenuController的sharedMenuController獲取全局menu-controller實例
2 計算選擇的邊界,并使用得到的矩形調用setTargetRect:inView:方法,則會根據此矩形與屏幕頂部或者底部的距離判斷菜單是出現在矩形上面或者下面
3 調用setMenuVisible:animated:方法以顯示menu
更多可參見iOS document
向編輯菜單添加自定義item
可以添加自定義item到菜單中,并通過target-action來完成action操作,當然,為了action能夠在responder chain中找到,通常需要將你的view becomeFirstResponder,因為UIMenuItem的形式是[[UIMenuItem alloc] initWithTitle:@"Change Color" action:@selector(changeColor:)],隨后將其放置進menu controller的menuItems中。
dismiss編輯菜單
[UIMenuController sharedMenuController].menuVisible = YES;通常菜單是在菜單項命令處理完之后自動隱藏的,但可以用這句來手動將其顯示。當然,如果有場景需要根據菜單狀態來處理,則可以監聽UIMenuControllerWillHideMenuNotification這個通知。
Text Kit繪制及管理文本
UITextField,UITextView,UILabel,UIWebView用來顯示文本,UITextView用來顯示大塊文本,其底層是強大的layout引擎,稱為Text Kit。如果想自定義layout過程或者干預這個過程,可以使用Text Kit,對于少量點的文本和需要自定義方案的特殊需求,可以選擇更底層的技術core text,本節之后后講到。
Text Kit是一組UIKit下用來使向app提供高質量的存儲,布局和顯示文本這類排版服務的類和協議,可以提供諸如kerning,連體字符,斷行和justification等這些精細的排版功能。Text Kit是基于Core Text的,所以有一樣高效的性能,UITextView是完全與Text Kit整合的,它提供了編輯和顯示的功能使用戶可以輸入文本,指定格式屬性并看到結果。其他Text Kit類提供文本存儲和布局功能。
基本的text kit 對象
NSTextStorage存儲用于顯示的文本,由NSLayoutManager將其顯示在NSTextContainer所指示的區域內。通常NSTextContainter定義文本顯示的區域,通常是矩形區域,但可以通過繼續它從而指定圓形,五邊形等非矩形。text container不僅定義了文本顯示區域的輪廓,也維護了一個貝塞爾路徑的數組以定義不可布局文本的區域。因此在布局的時候,文本流會圍繞不可布局的路徑,以此引入graphics及非文本布局的因素。
NSTextStorage是NSMutableAttributedString子類,是存儲文本及其屬性的基礎存儲機制。確保文本及屬性在編輯過程中處于一致的狀態。除了存儲文本之外,NSTextStorage對象也管理一組client NSLayoutManager對象,對其字符發生的任何變化,對這些manager進行通知,以對文本進行及時的relay和redisplay。
NSLayoutManager整體調度其它文本處理對象的操作,調解將NSTextStorage中的數據到view顯示區域的文本的所有操作過程。它將Unicode字符轉換成glyph,并監督glyph在NSTextContainer對象所定義的區域中布局的過程。(需要注意的是,layout manager,text storage,text container可以從子線程訪問,只要app guarantees the access from a single thread
文本屬性
Text Kit處理三種文本屬性:字符屬性,paragraph屬性和文檔屬性。字符屬性包括font,顏色和下標等特性,即單個字符或者一組字符的相關特性。paragraph是比如縮進,制表符和line spacing等屬性。文檔屬性包括紙張大小,頁邊距,和視角放大百分比等文檔屬性。
字符屬性
attributed字符串使用NSDictionary存儲鍵值對形式的字符屬性,key是字符串常量表示的屬性名,比如NSFontAttributeName
概念上,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屬性的變化,便于重新布局。
Font對象
計算機font是一個使用諸如open type或者true type格式存儲的數據文件,包含glyph相關的信息,以及繪制會用到的所有增補信息。創建UIFont時不使用alloc/init,相反使用preferredFontForTextStyle:傳入text style常數,或者fontWithName:size:,也可以用font描述符fontWithDescriptor:size:。
text style
從iOS7中引入,是由Dynamic Type機制實現的基于語義的font描述,使用它的好處在于可以獲取dynamic type為文本可讀性帶來的好處,因為dynamic type的響應是基于用戶偏好,增強辨識度及超大號類型的 權限設置的。
font descriptor
font描述符UIFontDescriptor類對象,由一組屬性dictionary創建UIFont對象。可以用它查詢系統支持的特定特征對應的所有字體,比如字體名,特性,語言及其他特征等。
激活font特性
font描述符的另一個作用是在一堆font特性中激活及選擇。font特性(font features)是字體的排版特性,控制glyph的渲染,只有在字體設計者支持時才會有相應特性。
font特性歸類為feature types,使用特定特性選擇符選特定特性設定。feature type可以是排他也可以是非排他的,如果是排他的,則一次只能選擇一個可能的feature 選擇符,比如數字是成比例還是等寬的,如果是非排他的,則一次可以選多個,比如連體字符feature類型。
feature有上下文相關和非相關之分,上下文相關的feature應用于glyph的方式是依賴于其鄰接的glyph的,ios文本系統layout能力的強大之處在于可以自動進行復雜的上下文處理。
非上下文相關特性應用方式不依賴于鄰接glyph,這些特性包括所選擇文本在選擇后呈現另一套glyph,以及為了數學排版或者增加排版復雜度而進行的glyph替換。
上述代碼激活了number spacing特性(由kNumberSpacingType常數所標識),選擇的是比例寬度數字(kProportionalNumbersSelector),同時激活了字符替換特性類型(kCharacterAlternativesType)選擇了值2,這個例子中用來表示字體特性類型和selector的是在core text框架中SFNTLayoutTypes.h中定義的枚舉。字符alternative類型沒有預定義的常量代表特性選擇標識,所以要用font-defined數值。由于font特性是由font定義的,所以可以直接查詢其支持的特性,使用CTFontCopyFeatures函數。
查詢font metric
可使用ascender, capHeight, xHeight等屬性查UIFont的metric信息
布局文本
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:所請求的區域與這些路徑包圍的區域重疊的時候,會返回調整過的區域。
指定多頁和多列布局
最簡單的情況下,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會直接反映出來
更底層的文本處理技術
core text先等一等