iOS 7中文字排版和渲染引擎——Text Kit

Text Kit學(xué)習(xí)(入門和進(jìn)階): ?http://www.cocoachina.com/industry/20131028/7250.html

iOS 7中文字排版和渲染引擎——Text Kit:http://www.ituring.com.cn/tupubarticle/2542

http://www.cocoachina.com/industry/20131113/7342.html

http://www.cocoachina.com/industry/20131126/7417.html

TextKit:http://www.lxweimin.com/p/2f72a5fa99f1


在iOS 7之前,應(yīng)用中字體的大小用戶是不能設(shè)置的,而且開發(fā)人員要想實(shí)現(xiàn)多種樣式的文字排版是件非常麻煩的事情。在iOS 7之后,這些問題都解決了,Text Kit就是解決這些問題的鑰匙。本章將向大家介紹iOS 7中文字排版和渲染引擎——Text Kit。

9.1 Text Kit基礎(chǔ)

Text Kit最主要的作用就是為程序提供文字排版和渲染的功能。通過Text Kit可以對文字進(jìn)行存儲、布局,以更加精準(zhǔn)的排版方式來顯示文本內(nèi)容。Text Kit隸屬于UIKit框架,其中包含了一些文字排版的相關(guān)類和協(xié)議。

9.1.1 文字的排版和渲染

在iOS 7之前也有一種用于文字排版和渲染的技術(shù)——Core Text,而引入Text Kit的目的并非要取代Core Text。Core Text是面向底層的文字排版和渲染技術(shù),如果我們需要將文本內(nèi)容直接渲染到圖形上下文時,從性能角度考慮,最佳方案就是使用Core Text。但是從易用性角度考慮,使用Text Kit是最好的選擇,因?yàn)樗軌蛑苯邮褂肬IKit提供的一些文本控件,例如:UITextView、UILabel和UITextField,對文字進(jìn)行排版。

Text Kit具有很多優(yōu)點(diǎn):文本控件UITextView、UITextField和UILabel是構(gòu)建于Text Kit之上的。Text Kit完全掌控著文字的排版和渲染:可以調(diào)整字距、行距、文字大小,指定特定的字體,對文字進(jìn)行分頁或分欄,支持富文本編輯、自定義文字截斷,支持文字的換行、折疊和著色等處理,支持凸版印刷效果。

9.1.2 Text Kit架構(gòu)

在開始介紹Text Kit API之前,我們有必要理解一下iOS的文字渲染框架。從圖9-1可見,Text Kit是基于Core Text構(gòu)建的,它通過Core Text與Core Graphics進(jìn)行交互。而文本控件,如:UILabel、UITextField和UITextView,則構(gòu)建于Text Kit之上,可見這些文本控件可以利用Text Kit提供的API來對文字進(jìn)行排版和渲染處理。從圖9-1可見,我們也可以看到UIWebView是基于WebKit的,它不能使用Text Kit提供的功能。



圖9-1 iOS 7之后的文字渲染

圖9-2所示是iOS 7之前的文字渲染,可以看出在iOS 7之前沒有Text Kit。文本控件,如:UILabel、UITextField和UITextView是基于String Drawing和WebKit構(gòu)建的。其中String Drawing與Core Graphics直接通信。因此在iOS 7之前文本控件也可以實(shí)現(xiàn)多種樣式的文字排版,但是事實(shí)上是通過WebKit實(shí)現(xiàn)的。WebKit是一種瀏覽器內(nèi)核技術(shù),使用它進(jìn)行文字渲染會消耗掉比較多的內(nèi)存,對應(yīng)用的性能有一定的影響。


圖9-2 iOS 7之前的文字渲染

9.1.3 Text Kit中的核心類

我們在使用Text Kit時,會涉及如下核心類。

NSTextContainer。定義了文本可以排版的區(qū)域。默認(rèn)情況下是矩形區(qū)域,如果是其他形狀的區(qū)域,需要通過子類化NSTextContainer來創(chuàng)建。

NSLayoutManager。該類負(fù)責(zé)對文字進(jìn)行編輯排版處理,將存儲在NSTextStorage中的數(shù)據(jù)轉(zhuǎn)換為可以在視圖控件中顯示的文本內(nèi)容,并把字符編碼映射到對應(yīng)的字形上,然后將字形排版到NSTextContainer定義的區(qū)域中。

NSTextStorage。主要用來存儲文本的字符和相關(guān)屬性,是NSMutableAttributedString的子類(見圖9-3)。此外,當(dāng)NSTextStorage中的字符或?qū)傩园l(fā)生改變時,會通知NSLayoutManager,進(jìn)而做到文本內(nèi)容的顯示更新。

NSAttributedString。支持渲染不同風(fēng)格的文本。

NSMutableAttributedString。可變類型的NSAttributedString,是NSAttributedString的子類(見圖9-3)。



圖9-3 NSAttributedString類圖

NSLayoutManager、NSTextContainer、NSTextStorage之間究竟是什么關(guān)系呢?圖9-4所示文本控件通過它們實(shí)現(xiàn)了顯示文本內(nèi)容到屏幕上的過程。NSLayoutManager對象從NSTextStorage對象中取得文本內(nèi)容,進(jìn)行排版,然后把排版之后的文本放到NSTextContainer對象指定的區(qū)域上。最后再由一個文本控件從NSTextContainer中取出內(nèi)容顯示到屏幕中。


圖9-4 NSLayoutManager、NSTextContainer和NSTextStorage之間的關(guān)系

NSLayoutManager對象起到承上啟下的作用。還記得鉛字排版嗎?在沒有計算機(jī)排版的時代,排版工人都是通過這種方法實(shí)現(xiàn)的,他們從鉛字庫中找到特定字體的字母,然后把它放到活動字模中(見圖9-5),最后進(jìn)行印刷。這個過程可以很好地幫助我們理解NSLayoutManager、NSTextContainer和NSTextStorage之間的關(guān)系,其中NSLayoutManager對象相當(dāng)于排版工人,NSTextStorage對象相當(dāng)于特定字體的鉛字庫,而NSTextContainer對象就相當(dāng)于我們看到的活動字模。文本控件從NSTextContainer中取出內(nèi)容顯示到屏幕的過程就相當(dāng)于印刷的過程。



圖9-5 鉛字排版1

1該圖出自維基百科http://zh.wikipedia.org/wiki/File:MetalTypeZoomIn.JPG

9.1.4 實(shí)例:凸版印刷效果

為了更好地理解我們前面介紹的API內(nèi)容,下面我們通過一個實(shí)例介紹NSLayoutManager、NSTextContainer和NSTextStorage三者之間的關(guān)系。

在Xcode中選擇Single View Application模板,創(chuàng)建一個名為TextKit_Sample的工程,在創(chuàng)建時選擇Devices為Universal。工程創(chuàng)建成功后,打開Main_iPhone.storyboard故事板文件,從對象庫中拖曳TextView控件到設(shè)計視圖上,并修改其文本內(nèi)容,如圖9-6所示。


圖9-6 拖曳TextView控件

拖曳完成后,要為其定義輸出口屬性。ViewController.h文件代碼如下:

#import @interfaceViewController:UIViewController@property(nonatomic,strong)NSTextContainer*textContainer;①@property(strong,nonatomic)IBOutletUITextView*textView;②-(void)markWord:(NSString*)word inTextStorage:(NSTextStorage*)textStorage;③@end

上述代碼第①行聲明了NSTextContainer類型的屬性textContainer。代碼第②行聲明了TextView控件屬性。第③行代碼聲明一個方法,用于設(shè)置某些單詞樣式風(fēng)格。

ViewController.m文件中viewDidLoad方法代碼如下:

-(void)viewDidLoad{[superviewDidLoad];CGRecttextViewRect=CGRectInset(self.view.bounds,10.0,20.0);①NSTextStorage*textStorage=[[NSTextStoragealloc]initWithString:_textView.text];②NSLayoutManager*layoutManager=[[NSLayoutManageralloc]init];③[textStorage addLayoutManager:layoutManager];④_textContainer=[[NSTextContaineralloc]initWithSize:textViewRect.size];⑤[layoutManager addTextContainer:_textContainer];⑥[_textView removeFromSuperview];⑦_(dá)textView=[[UITextViewalloc]initWithFrame:textViewRect

textContainer:_textContainer];⑧[self.view addSubview:_textView];⑨//設(shè)置凸版印刷效果[textStorage beginEditing];⑩NSDictionary*attrsDic=@{NSTextEffectAttributeName:NSTextEffectLetterpressStyle};NSMutableAttributedString*attrStr=[[NSMutableAttributedStringalloc]initWithString:_textView.text attributes:attrsDic];[textStorage setAttributedString:attrStr];[selfmarkWord:@"我"inTextStorage:textStorage];[selfmarkWord:@"I"inTextStorage:textStorage];[textStorage endEditing];}

上述代碼第①行是創(chuàng)建一個矩形區(qū)域,這個區(qū)域是通過CGRectInset函數(shù)創(chuàng)建的,這個函數(shù)能夠指定一個中心點(diǎn),后面的兩個參數(shù)沿著self.view.bounds區(qū)域向內(nèi)縮進(jìn)量。這樣可以使得文字部分不會太靠近視圖的邊界。

第②行代碼是創(chuàng)建NSTextStorage對象,它需要一個字符串作為構(gòu)造方法的參數(shù),這里我們是從TextView控件取出來賦值給它的。第③行代碼是創(chuàng)建NSLayoutManager對象。第④行代碼是將剛剛創(chuàng)建的NSTextStorage和NSLayoutManager對象關(guān)聯(lián)起來。第⑤行代碼是創(chuàng)建NSTextContainer對象,在創(chuàng)建它的時候,通過構(gòu)造方法設(shè)置它的區(qū)域,我們這里設(shè)置的區(qū)域與TextView區(qū)域是相同的。第⑥行代碼是將剛剛創(chuàng)建的NSLayoutManager和NSTextContainer對象關(guān)聯(lián)起來。

第⑦~⑨行代碼是重新構(gòu)建原來的TextView控件,并且重新添加到視圖上。這主要是因?yàn)橹挥兄匦聞?chuàng)建代碼才能通過Text Kit中NSLayoutManager來管理,而原來在Interface Builder中創(chuàng)建的TextView控件不再使用了。

第⑩~?行代碼是實(shí)現(xiàn)設(shè)置凸版印刷效果,這些設(shè)置代碼是需要放在[textStorage beginEditing]和[textStorage endEditing]之間的。第?行代碼是聲明一個字典對象,其中包括@{NSTextEffectAttribute Name:NSTextEffectLetterpressStyle},NSTextEffectAttributeName是文本效果鍵,而NSTextEffectLetterpressStyle是文本效果值,這里面它們都是常量。第?行代碼是創(chuàng)建NSMutableAttributedString對象,在構(gòu)造方法中需要指定要設(shè)置的文本以及文本的樣式。第?行代碼是將通過NSTextStorage對象的setAttributedString:方法設(shè)置文本NSTextStorage還有類似的方法addAttributedString:,該方法是添加新的設(shè)置文本,這樣可以疊加多種效果。第?~?行代碼是調(diào)用markWord:inTextStorage:方法實(shí)現(xiàn)特定單詞的查找,并添加設(shè)置效果。

ViewController.m文件中的markWord:inTextStorage:方法代碼如下:

-(void)markWord:(NSString*)word inTextStorage:(NSTextStorage*)textStorage{NSRegularExpression*regex=[NSRegularExpressionregularExpressionWithPattern:word

options:0error:nil];①NSArray*matches=[regex matchesInString:_textView.text

options:0range:NSMakeRange(0,[_textView.text length])];②for(NSTextCheckingResult*matchinmatches){③NSRangematchRange=[match range];[textStorage addAttribute:NSForegroundColorAttributeNamevalue:[UIColorredColor]range:matchRange];④}}

上述代碼第①行是創(chuàng)建正則表達(dá)式NSRegularExpression對象,其中的regularExpressionWithPattern參數(shù)指定正則表達(dá)式。第②行代碼通過正則表達(dá)式NSRegularExpression對象對TextView中的文本內(nèi)容進(jìn)行掃描,結(jié)果放到數(shù)組中。第③行代碼從集合中取出NSTextCheckingResult結(jié)果對象。第④行代碼是為找到的文本設(shè)置顏色為紅色風(fēng)格。

編碼完成之后我們就可以運(yùn)行一下看看效果了,如圖9-7所示,其中的“我”和“I”是紅色顯示的,整個的文字設(shè)置為凸版印刷效果。圖9-7 運(yùn)行效果


9.2 文字圖片混合排版

讀者喜歡閱讀圖文并茂的文章,因此在應(yīng)用界面中,有時不僅僅要有文字,還要有圖片,這就涉及文字和圖片的混排了。在圖文混排過程中必然會涉及文字環(huán)繞圖片的情況,很多文字處理軟件(如Word、WPS、Open Office等)都有這種功能。Text Kit通過環(huán)繞路徑(exclusion paths)將文字按照指定的路徑環(huán)繞在圖片等視圖對象的 周圍(見圖9-8)。


圖9-8 環(huán)繞路徑

下面我們看看如何通過環(huán)繞路徑實(shí)現(xiàn)文字圖片混合排版。我們可以在上一節(jié)的案例基礎(chǔ)上修改,打開Main_iPhone.storyboard故事板文件,從對象庫中拖曳ImageView控件到設(shè)計視圖上,如圖9-9所示,通過設(shè)置Image屬性設(shè)置要顯示的圖片為MetalType.png,當(dāng)然我們之前需要將圖片導(dǎo)入到工程中。

圖9-9 拖曳ImageView到設(shè)計視圖

我們看看具體代碼,ViewController.m文件主要代碼如下:

-(void)viewDidLoad{[superviewDidLoad];CGRecttextViewRect=CGRectInset(self.view.bounds,10.0,20.0);NSTextStorage*textStorage=[[NSTextStoragealloc]initWithString:_textView.text];NSLayoutManager*layoutManager=[[NSLayoutManageralloc]init];[textStorage addLayoutManager:layoutManager];_textContainer=[[NSTextContaineralloc]initWithSize:textViewRect.size];[layoutManager addTextContainer:_textContainer];[_textView removeFromSuperview];_textView=[[UITextViewalloc]initWithFrame:textViewRect

textContainer:_textContainer];[self.view insertSubview:_textView belowSubview:_imageView];①//設(shè)置凸版印刷效果[textStorage beginEditing];NSDictionary*attrsDic=@{NSTextEffectAttributeName:NSTextEffectLetterpressStyle};NSMutableAttributedString*attrStr=[[NSMutableAttributedStringalloc]initWithString:_textView.text attributes:attrsDic];[textStorage setAttributedString:attrStr];[textStorage endEditing];_textView.textContainer.exclusionPaths=@[[selftranslatedBezierPath]];②}-(UIBezierPath*)translatedBezierPath③{CGRectimageRect=[self.textView convertRect:_imageView.frame fromView:self.view];④UIBezierPath*newPath=[UIBezierPathbezierPathWithRect:imageRect];⑤returnnewPath;}

上述代碼第①行是重新將TextView控件添加到View上,但是又必須考慮到不遮擋ImageView,因此需要使用View的insertSubview:belowSubview:方法添加,belowSubview指定ImageView,這樣新添加的TextView就在ImageView之下了。第②行代碼是使用TextView的_textView.textContainer.exclusionPath屬性指定環(huán)繞路徑,該屬性是NSArray類型,也就是說可以設(shè)定多個環(huán)繞路徑,而且在NSArray數(shù)組中元素是一種UIBezierPath類型。

注意UIBezierPath類可以創(chuàng)建基于貝塞爾曲線2路徑,此類是Core Graphics框架關(guān)于圖形繪制路徑的一個封裝,使用此類可以定義簡單的形狀,如橢圓、矩形,或者由多個直線和曲線段組成的形狀。

2貝賽爾(Bézier)曲線是法國數(shù)學(xué)家貝塞爾在工作中發(fā)現(xiàn),任何一條曲線都可以通過與它相切的控制線兩端的點(diǎn)的位置來定義。因此,貝賽爾曲線可以用4個點(diǎn)描述,其中兩個點(diǎn)描述兩個端點(diǎn),另外兩個描述每一端的切線。貝賽爾曲線可以分為:二次方貝賽爾曲線和高階貝賽爾曲線。

獲得ImageView的貝塞爾曲線路徑是通過第③行代碼的translatedBezierPath方法實(shí)現(xiàn)的。代碼中的第④行是進(jìn)行坐標(biāo)系轉(zhuǎn)換,如圖9-10所示,原來ImageView的坐標(biāo)系是相對于View的坐標(biāo)(62, 72),通過convertRect: fromView:方法將坐標(biāo)系轉(zhuǎn)換為相對于TextView的坐標(biāo)(52, 52)。這是因?yàn)槲覀冃枰@得的是TextView的文字圍繞ImageView路徑,因此坐標(biāo)系需要參照TextView。圖9-10 坐標(biāo)系的轉(zhuǎn)換

編碼完成之后我們就可以運(yùn)行一下看看效果了。

9.3 動態(tài)字體

以前的iOS用戶會抱怨,為什么不能設(shè)置自定義字體呢?在iOS 7系統(tǒng)之后蘋果對于字體在顯示上做了一些優(yōu)化,讓不同大小的字體在屏幕上都能清晰地顯示。通常用戶設(shè)置了自己偏好的字體了,用戶可以在圖9-11所示的步驟(設(shè)置→通用→輔助功能)設(shè)置粗體文字的過程。用戶還可以在圖9-12所示的步驟(設(shè)置→通用→文字大小)是設(shè)置文字大小的過程。


圖9-11 設(shè)置粗體文本


圖9-12 設(shè)置文字大小

但是并不是在設(shè)置中進(jìn)行設(shè)置就萬事大吉了,我們還要在應(yīng)用代碼中進(jìn)行編程,以應(yīng)對這些變化。我們需要在應(yīng)用中給文本控件設(shè)置為用戶設(shè)置的字體,而不是在代碼中硬編碼字體及大小。iOS 7中可以通過UIFont中新增的preferredFontForTextStyle:方法來獲取用戶設(shè)置的字體。

iOS 7中提供了6種字體樣式供選擇。

UIFontTextStyleHeadline。標(biāo)題字體,例如:報紙的標(biāo)題。

UIFontTextStyleSubheadline。子標(biāo)題字體。

UIFontTextStyleBody。正文字體。

UIFontTextStyleFootnote。腳注字體。

UIFontTextStyleCaption1。標(biāo)題字體,一般用于照片或者字幕。

UIFontTextStyleCaption2。另一個可選Caption字體。

這6種字體具體樣式可見圖9-13所示。


圖9-13 iOS系統(tǒng)提供的6種字體樣式

處理系統(tǒng)提供了6種樣式的字體,我們還可以自己定義字體。

當(dāng)用戶在設(shè)置中改變了字體,系統(tǒng)會給應(yīng)用程序發(fā)送UIContentSizeCategoryDidChangeNotification通知,我們需要監(jiān)聽這個通知,并通過下面的代碼重新設(shè)置文本控件字體即可。

self.textView.font=[UIFontpreferredFontForTextStyle:UIFontTextStyleBody];

為了能夠更好地理解動態(tài)字體,下面我們通過一個實(shí)例介紹一下。我們對9.1.3節(jié)的案例修改一下,我們看看具體代碼,ViewController.m文件主要代碼如下:

-(void)viewDidLoad{[superviewDidLoad];CGRecttextViewRect=CGRectInset(self.view.bounds,10.0,20.0);NSTextStorage*textStorage=[[NSTextStoragealloc]initWithString:_textView.text];NSLayoutManager*layoutManager=[[NSLayoutManageralloc]init];[textStorage addLayoutManager:layoutManager];_textContainer=[[NSTextContaineralloc]initWithSize:textViewRect.size];[layoutManager addTextContainer:_textContainer];[_textView removeFromSuperview];_textView=[[UITextViewalloc]initWithFrame:textViewRect

textContainer:_textContainer];[self.view addSubview:_textView];//設(shè)置凸版印刷效果[textStorage beginEditing];NSDictionary*attrsDic=@{NSTextEffectAttributeName:NSTextEffectLetterpressStyle};NSMutableAttributedString*attrStr=[[NSMutableAttributedStringalloc]initWithString:_textView.text attributes:attrsDic];[textStorage setAttributedString:attrStr];[selfmarkWord:@"我"inTextStorage:textStorage];[selfmarkWord:@"I"inTextStorage:textStorage];[textStorage endEditing];[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(preferredContentSizeChanged:)name:UIContentSizeCategoryDidChangeNotificationobject:nil];①}-(void)preferredContentSizeChanged:(NSNotification*)notification{②self.textView.font=[UIFontpreferredFontForTextStyle:UIFontTextStyleBody];③}

在上述代碼第①行是注冊監(jiān)聽UIContentSizeCategoryDidChangeNotification通知,當(dāng)系統(tǒng)發(fā)出這個通知后,會回調(diào)preferredContentSizeChanged:方法。代碼第②行所定義的就是preferredContentSizeChanged:回調(diào)方法。在這個方法中我們通過第③行代碼實(shí)現(xiàn)重新設(shè)置TextView的字體樣式。

編碼完成之后我們就可以運(yùn)行一下看看效果了,如圖9-14所示是運(yùn)行之后通過系統(tǒng)設(shè)置改變文字大小前后的對比。




圖9-14 改變文字大小前后

在這個案例基礎(chǔ)上大家可以改變不同的字體風(fēng)格看看運(yùn)行的效果。

9.4 小結(jié)

在本章中,我們首先介紹了iOS 7的Text Kit技術(shù),通過Text Kit技術(shù)我們實(shí)現(xiàn)了文本圖片混合排版,動態(tài)字體設(shè)置等。







轉(zhuǎn)自TracyYih的博客

更詳細(xì)的內(nèi)容可以參考官方文檔 《Text Programming Guide for iOS》。

“Text Kit指的是UIKit框架中用于提供高質(zhì)量排版服務(wù)的一些類和協(xié)議,它讓程序能夠存儲,排版和顯示文本信息,并支持排版所需要的所有特性,包括字距調(diào)整、連寫、換行和對齊等。”

以前,如果我們想實(shí)現(xiàn)復(fù)雜的文本排版,例如在textView中顯示不同樣式的文本,或者圖片和文字混排,你可能就需要借助于UIWebView或者深入研究一下Core Text。在iOS6中,UILabel、UITextField、UITextView增加了一個NSAttributedString屬性,可以稍微解決一些排版問題,但是支持的力度還不夠。現(xiàn)在Text Kit完全改變了這種現(xiàn)狀。

Text Kit是基于Core Text構(gòu)建的快速、先進(jìn)的文本排版和渲染引擎,并且與UIKit很好的集合。UITextView,UITextField、UILabel都已經(jīng)基于Text Kit重新構(gòu)建,所以它們都支持分頁文本、文本包裝、富文本編輯、交互式文本著色、文本折疊和自定義截取等特性。所有這些UI控件現(xiàn)在都以同樣的方式構(gòu)建,在它們后面,一個NSTextStorage對象保存著文本的主要信息,它本身是NSMutableAttributedString的子類,支持分批編輯。這就意味著你可以改變一個范圍內(nèi)的字符的樣式而不用整體替換文本內(nèi)容。

[self.textView.textStorage?beginEditing];

[self?markWord:@"Alice"inTextStorage:self.textView.textStorage];

[self.textView.textStorage?endEditing];

Text storage管理者一系列的NSLayoutManager對象,當(dāng)它的字符或者屬性改變時會通知到自己所管理的layout Manager對象以便它們作出相應(yīng)的反應(yīng)。在layout manager上面是一個NSTextContainer對象,用于為layout manager定義坐標(biāo)系和一些幾何特性。例如,如果你想UITextView中的文本環(huán)繞在一張圖片四周,你可以給text container設(shè)定一個排除路徑(exclusion path)。

UIBezierPath?*exclusion?=?ButterflyBezierPath;

self.textView.textContainer.exclusionPaths?=?@[exclusion];

Text container能夠處理擊中測試(hit tests),所以可以定位到點(diǎn)擊的字符在文本中的位置。此外它還提供一些代理方法讓開發(fā)者能夠自己定義鏈接點(diǎn)擊后的處理事件。

通過基于Text Kit重新構(gòu)建UILabel、UITextField和UITextView,蘋果給開發(fā)者更大的靈活性和能力來設(shè)計富文本視圖,同時簡化了這些控件的使用,因?yàn)樗鼈兪且酝瑯拥姆绞皆O(shè)計的,所有這些好處都是站在巨人(Core Text)的肩上。通常更強(qiáng)大的功能和靈活性也就意味著需要更多的設(shè)置和管理,但是,如果你只是想顯示一段簡單的文本,你還是可以像以前一樣使用。

self.textLabel.text?=?@"Hello?Text?Kit";

本文翻譯自《iOS 7: Text Kit

Text Kit進(jìn)階

上一篇文章Text Kit入門中我們主要了解了什么是Text Kit及它的一些架構(gòu)和基本特性,這篇文章中會涉及關(guān)于Text Kit的更多具體應(yīng)用。

Text Kit是建立在Core Text框架上的,我們知道CoreText.framework是一個龐大而復(fù)雜的框架,而Text Kit在繼承了Core Text強(qiáng)大功能的同時給開發(fā)者提供了比較友好的面向?qū)ο蟮腁PI。

本文主要介紹Text Kit下面四個特性:

動態(tài)字體(Dynamic type)

凸版印刷體效果(Letterpress effects)

路徑排除(Exclusion paths)

動態(tài)文本格式化和存儲(Dynamic text formatting and storage)

動態(tài)字體(Dynamic type)

動態(tài)字體是iOS7中新增加的比較重要的特性之一,程序應(yīng)該按照用戶設(shè)定的字體大小和粗細(xì)來顯示文本內(nèi)容。

分別在設(shè)置\通用\輔助功能和設(shè)置\通用\文字大小中可以設(shè)置文本在應(yīng)用程序中顯示的粗細(xì)和大小。

iOS7對系統(tǒng)字體在顯示上做了一些優(yōu)化,讓不同大小的字體在屏幕上都能清晰的顯示。通常用戶設(shè)置了自己偏好的字體,他們希望在所有程序中都看到文本顯示是根據(jù)他們的設(shè)定進(jìn)行調(diào)整。為了實(shí)現(xiàn)這個,開發(fā)者需要在自己的應(yīng)用中給文本控件設(shè)置當(dāng)前用戶設(shè)置字體,而不是指定死字體及大小。可以通過UIFont中新增的preferredFontForTextStyle:方法來獲取用戶偏好的字體。

iOS7中給出了6中字體樣式供選擇:

UIFontTextStyleHeadline

UIFontTextStyleBody

UIFontTextStyleSubheadline

UIFontTextStyleFootnote

UIFontTextStyleCaption1

UIFontTextStyleCaption2

為了讓我們的程序支持動態(tài)字體,需要按一下方式給文本控件(通常是指UILabel,UITextField,UITextView)設(shè)定字體:

self.textView.font?=?[UIFont?preferredFontForTextStyle:UIFontTextStyleBody];

這樣設(shè)置之后,文本控件就會以用戶設(shè)定的字體大小及粗細(xì)顯示,但是如果程序在運(yùn)行時,用戶切換到設(shè)置里修改了字體,這是在切回程序,字體并不會自動跟著變。這時就需要我們自己來更新一下控件的字體了。

在系統(tǒng)字體修改時,系統(tǒng)會給運(yùn)行中的程序發(fā)送UIContentSizeCategoryDidChangeNotification通知,我們只需要監(jiān)聽這個通知,并重新設(shè)置一下字體即可。

[[NSNotificationCenter?defaultCenter]?addObserver:self

selector:@selector(preferredContentSizeChanged:)

name:UIContentSizeCategoryDidChangeNotification

object:nil];

-?(void)preferredContentSizeChanged:(NSNotification?*)notification{

self.textView.font?=?[UIFont?preferredFontForTextStyle:UIFontTextStyleBody];

}

當(dāng)然,有的時候要適應(yīng)動態(tài)修改的字體并不是這么設(shè)置一下就完事了,控件的大小可能也需要進(jìn)行相應(yīng)的調(diào)整,這時我們程序中的控件大小也不應(yīng)該寫死,而是需要根據(jù)字體大小來計算.

凸版印刷體效果(Letterpress effects)

凸版印刷替效果是給文字加上奇妙陰影和高光,讓文字看起有凹凸感,像是被壓在屏幕上。當(dāng)然這種看起來很高端大氣上檔次的效果實(shí)現(xiàn)起來確實(shí)相當(dāng)?shù)暮唵危恍枰oAttributedString加一個NSTextEffectAttributeName屬性,并指定該屬性值為NSTextEffectLetterpressStyle就可以了。

tionary?*attributes?=?@{

NSForegroundColorAttributeName:?[UIColor?redColor],

NSFontAttributeName:?[UIFont?preferredFontForTextStyle:UIFontTextStyleHeadline],

NSTextEffectAttributeName:?NSTextEffectLetterpressStyle

};

self.titleLabel.attributedText?=?[[NSAttributedString?alloc]?initWithString:@"Title"attributes:attributes];

在iOS7系統(tǒng)自帶的備忘錄應(yīng)用中,蘋果就使用了這種凸版印刷體效果。

路徑排除(Exclusion paths)

在排版中,圖文混排是非常常見的需求,但有時候我們的圖片并一定都是正常的矩形,這個時候我們?nèi)绻枰獙⑽谋经h(huán)繞在圖片周圍,就可以用路徑排除(exclusion paths)了。

Explosion pats基本原理是將需要被文本留出來的形狀的路徑告訴文本控件的NSTextContainer對象,NSTextContainer在文字排版時就會避開該路徑。

UIBezierPath?*floatingPath?=?[self?pathOfImage];

self.textView.textContainer.exclusionPaths?=?@[floatingPath];

所以實(shí)現(xiàn)Exclusion paths的主要工作就是獲取這個path。

動態(tài)文本格式化和存儲(Dynamic text formatting and storage)

好了,到現(xiàn)在我們知道了Text Kit可以動態(tài)的根據(jù)用戶設(shè)置的字體大小進(jìn)行調(diào)整,但是如果具體某個文本顯示控件中的文本樣式能夠動態(tài)調(diào)整是不是會更酷一些呢?

實(shí)現(xiàn)這些才是真正體現(xiàn)Text Kit強(qiáng)大之處的時候,在此之前你需要理解Text Kit中的文本存儲系統(tǒng)是怎么工作的,下圖顯示了Text Kit中文本的保存、渲染和現(xiàn)實(shí)之間的關(guān)系。

當(dāng)你使用UITextView、UILabel、UITextField控件的時候,系統(tǒng)會自動創(chuàng)建上面這些類,你可以選擇直接使用這么默認(rèn)的實(shí)現(xiàn)或者為你的控件自定義這幾個中的任何一個。

1.NSTextStorage本身繼承與NSMutableAttributedString,它是以attributed string的形式保存需要渲染的文本,并在文本內(nèi)容改變的時候通知到對應(yīng)的layout manager對象。通常你需要創(chuàng)建NSTextStorage的子類來在文本改變時進(jìn)行文本顯示樣式的更新。

2.NSLayoutManager作為文本控件中的排版引擎接收保存的文本并在屏幕上渲染出來。

3.NSTextContainer描述了文本在屏幕上顯示時的幾何區(qū)域,每個text container與一個具體的UITextView相關(guān)聯(lián)。如果你需要定義一個很復(fù)雜形狀的區(qū)域來顯示文本,你可能需要創(chuàng)建NSTextContainer子類。

要實(shí)現(xiàn)我們上面描述的動態(tài)文本格式化功能,我們需要創(chuàng)建NSTextStorage子類以便在用戶輸入文本的時候動態(tài)的增加文本屬性。自定義了text storage后,我們需要替換調(diào)UITextView默認(rèn)的text storage。

創(chuàng)建NSTextStorage的子類

我們創(chuàng)建NSTextStorage子類,命名為MarkupTextStorage,在實(shí)現(xiàn)文件中添加一個成員變量:

#import?"MarkupTextStorage.h"

@implementation?MarkupTextStorage

{

NSMutableAttributedString?*_backingStore;

}

-?(id)init

{

self?=?[superinit];

if(self)?{

_backingStore?=?[[NSMutableAttributedString?alloc]?init];

}

returnself;

}

@end

NSTextStorage的子類需要重載一些方法提供NSMutableAttributedString類型的backing store信息,所以我們繼續(xù)添加下面代碼:

-?(NSString?*)string

{

return[_backingStore?string];

}

-?(NSDictionary?*)attributesAtIndex:(NSUInteger)location?effectiveRange:(NSRangePointer)range

{

return[_backingStore?attributesAtIndex:location?effectiveRange:range];

}

-?(void)replaceCharactersInRange:(NSRange)range?withString:(NSString?*)str

{

[self?beginEditing];

[_backingStore?replaceCharactersInRange:range?withString:str];

[self?edited:NSTextStorageEditedCharacters?|?NSTextStorageEditedAttributes

range:range?changeInLength:str.length?-?range.length];

[self?endEditing];

}

-?(void)setAttributes:(NSDictionary?*)attrs?range:(NSRange)range

{

[self?beginEditing];

[_backingStore?setAttributes:attrs?range:range];

[self?edited:NSTextStorageEditedAttributes

range:range?changeInLength:0];

[self?endEditing];

}

后面兩個方法都是代理到backing store,然后需要被beginEditing edited endEditing包圍,而且必須在文本編輯時按順序調(diào)用來通知text storage對應(yīng)的layout manager。

你可能發(fā)現(xiàn)子類化NSTextStorage需要寫不少的代碼,因?yàn)镹STextStorage是一個類集群中的一個開發(fā)接口,不能只是繼承它然后重載很少的方法來拓展它的功能,而是需要自己實(shí)現(xiàn)很多細(xì)節(jié)。

類集群(Class cluster)是蘋果Cocoa(Touch)框架中常用的設(shè)計模式之一。

類集群是Objective-C中對抽象工廠模式的簡單實(shí)現(xiàn),為創(chuàng)建一些列相關(guān)或獨(dú)立對象提供了統(tǒng)一的接口而不用指定具體的類。常用的像NSArray和NSNumber事實(shí)上也是一系列類集群的開放接口。

蘋果使用類集群是為了將一些類具體類隱藏在開放的抽象父類之下,外面通過抽象父類的方法來創(chuàng)建私有子類的實(shí)例,并且外界也完全不知道工廠分配到了哪個私有類,因?yàn)樗鼈兪冀K只和開放接口交互。

使用類集群確實(shí)簡化了接口,讓類更容易被使用,但是要知道魚和熊掌不可兼得,你又想簡單又想可拓展性強(qiáng),哪有那么好的事啊?所以創(chuàng)建一個類集群中的抽象父類就沒有那么簡單了。

好了,上面解釋了這么多其實(shí)主要就說明了為什么子類化NSTextStorage需要寫這么多代碼,下面要在UITextView使用我們自定義的text storage了。

設(shè)置UITextView

-?(void)createMarkupTextView

{

NSDictionary?*attributes?=?@{NSFontAttributeName:?[UIFont?preferredFontForTextStyle:UIFontTextStyleBody]};

NSString?*content?=?[NSString?stringWithContentsOfFile:[[NSBundle?mainBundle]?pathForResource:@"content"ofType:@"txt"]

encoding:NSUTF8StringEncoding

error:nil];

NSAttributedString?*attributedString?=?[[NSAttributedString?alloc]?initWithString:content

attributes:attributes];

_textStorage?=?[[MarkupTextStorage?alloc]?init];

[_textStorage?setAttributedString:attributedString];

CGRect?textViewRect?=?CGRectMake(20,?60,?280,?self.view.bounds.size.height?-?100);

NSLayoutManager?*layoutManager?=?[[NSLayoutManager?alloc]?init];

NSTextContainer?*textContainer?=?[[NSTextContainer?alloc]?initWithSize:CGSizeMake(textViewRect.size.width,?CGFLOAT_MAX)];

[layoutManager?addTextContainer:textContainer];

[_textStorage?addLayoutManager:layoutManager];

_textView?=?[[UITextView?alloc]?initWithFrame:textViewRect

textContainer:textContainer];

_textView.delegate?=?self;

[self.view?addSubview:_textView];

}

很長的代碼,下面我們來看看都做了些啥:

1.創(chuàng)建了一個自定義的text storage對象,并通過attributed string保存了需要顯示的內(nèi)容;

2.創(chuàng)建了一個layout manager對象;

3.創(chuàng)建了一個text container對象并將它與layout manager關(guān)聯(lián),然后該text container再和text storage對象關(guān)聯(lián);

4.通過text container創(chuàng)建了一個text view并顯示。

你可以將代碼和前面那對象間的關(guān)系圖對應(yīng)著理解一下。

動態(tài)格式化

繼續(xù)在MarkupTextStorage.m文件中添加如下方法:

-?(void)processEditing

{

[self?performReplacementsForRange:[self?editedRange]];

[superprocessEditing];

}

processEditing在layout manager中文本修改時發(fā)送通知,它通常也是處理一些文本修改邏輯的好地方。

繼續(xù)添加:

-?(void)performReplacementsForRange:(NSRange)changedRange

{

NSRange?extendedRange?=?NSUnionRange(changedRange,?[[_backingStore?string]

lineRangeForRange:NSMakeRange(changedRange.location,?0)]);

extendedRange?=?NSUnionRange(changedRange,?[[_backingStore?string]

lineRangeForRange:NSMakeRange(NSMaxRange(changedRange),?0)]);

[self?applyStylesToRange:extendedRange];

}

這個方法用于擴(kuò)大文本匹配的范圍,因?yàn)閏hangedRange只是標(biāo)識出一個字符,lineRangeForRange會將范圍擴(kuò)大到當(dāng)前的一整行。

下面就剩下匹配特定格式的文本來顯示對應(yīng)的樣式了:

-?(NSDictionary*)createAttributesForFontStyle:(NSString*)style

withTrait:(uint32_t)trait?{

UIFontDescriptor?*fontDescriptor?=?[UIFontDescriptor

preferredFontDescriptorWithTextStyle:UIFontTextStyleBody];

UIFontDescriptor?*descriptorWithTrait?=?[fontDescriptor

fontDescriptorWithSymbolicTraits:trait];

UIFont*?font?=??[UIFont?fontWithDescriptor:descriptorWithTrait?size:?0.0];

return@{?NSFontAttributeName?:?font?};

}

-?(void)createMarkupStyledPatterns

{

UIFontDescriptor?*scriptFontDescriptor?=

[UIFontDescriptor?fontDescriptorWithFontAttributes:

@{UIFontDescriptorFamilyAttribute:?@"Bradley?Hand"}];

//?1.?base?our?script?font?on?the?preferred?body?font?size

UIFontDescriptor*?bodyFontDescriptor?=?[UIFontDescriptor

preferredFontDescriptorWithTextStyle:UIFontTextStyleBody];

NSNumber*?bodyFontSize?=?bodyFontDescriptor.

fontAttributes[UIFontDescriptorSizeAttribute];

UIFont*?scriptFont?=?[UIFont

fontWithDescriptor:scriptFontDescriptor?size:[bodyFontSize?floatValue]];

//?2.?create?the?attributes

NSDictionary*?boldAttributes?=?[self

createAttributesForFontStyle:UIFontTextStyleBody

withTrait:UIFontDescriptorTraitBold];

NSDictionary*?italicAttributes?=?[self

createAttributesForFontStyle:UIFontTextStyleBody

withTrait:UIFontDescriptorTraitItalic];

NSDictionary*?strikeThroughAttributes?=?@{?NSStrikethroughStyleAttributeName?:?@1,

NSForegroundColorAttributeName:?[UIColor?redColor]};

NSDictionary*?scriptAttributes?=?@{?NSFontAttributeName?:?scriptFont,

NSForegroundColorAttributeName:?[UIColor?blueColor]

};

NSDictionary*?redTextAttributes?=

@{?NSForegroundColorAttributeName?:?[UIColor?redColor]};

_replacements?=?@{

@"(\\*\\*\\w+(\\s\\w+)*\\*\\*)":?boldAttributes,

@"(_\\w+(\\s\\w+)*_)":?italicAttributes,

@"(~~\\w+(\\s\\w+)*~~)":?strikeThroughAttributes,

@"(`\\w+(\\s\\w+)*`)":?scriptAttributes,

@"\\s([A-Z]{2,})\\s":?redTextAttributes

};

}

-?(void)applyStylesToRange:(NSRange)searchRange

{

NSDictionary*?normalAttrs?=?@{NSFontAttributeName:

[UIFont?preferredFontForTextStyle:UIFontTextStyleBody]};

//?iterate?over?each?replacement

for(NSString*?keyin_replacements)?{

NSRegularExpression?*regex?=?[NSRegularExpression

regularExpressionWithPattern:key

options:0

error:nil];

NSDictionary*?attributes?=?_replacements[key];

[regex?enumerateMatchesInString:[_backingStore?string]

options:0

range:searchRange

usingBlock:^(NSTextCheckingResult?*match,

NSMatchingFlags?flags,

BOOL?*stop){

//?apply?the?style

NSRange?matchRange?=?[match?rangeAtIndex:1];

[self?addAttributes:attributes?range:matchRange];

//?reset?the?style?to?the?original

if(NSMaxRange(matchRange)+1?<?self.length)?{

[self?addAttributes:normalAttrs

range:NSMakeRange(NSMaxRange(matchRange)+1,?1)];

}

}];

}

}

在createMarkupStyledPatterns初始化方法中調(diào)用createMarkupStyledPatterns,通過正則表達(dá)式來給特定格式的字符串設(shè)定特定顯示樣式,形成一個對應(yīng)的字典。然后在applyStylesToRange:中利用已定義好的樣式字典來給匹配的文本端增加樣式。

到這里本篇文章的內(nèi)容就結(jié)束了,其實(shí)前面三點(diǎn)都很簡單,稍微過一下就能用。最后一個動態(tài)文本格式化內(nèi)容稍微多一點(diǎn),可以結(jié)合我的代碼TextKitDemo來看。

參考鏈接:

http://www.raywenderlich.com/50151/text-kit-tutorial

https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/Introduction/Introduction.html

http://adcdownload.apple.com/wwdc_2013/wwdc_2013_sample_code/ios_intrototextkit.zi


Text Kit是iOS 7中引入的一個新功能,非常值得開發(fā)者使用,下面先看看本文的目錄結(jié)構(gòu):

什么是Text Kit

Text Kit架構(gòu)

Text Kit特點(diǎn)

Text Kit功能概述

Text Kit中重要的一些對象

Text Kit示例

小結(jié)

推薦Text Kit學(xué)習(xí)資源

什么是Text Kit

在iOS7中,蘋果引入了Text Kit--Text Kit是一個快速而又現(xiàn)代化的文字排版和渲染引擎。Text Kit在UIKit framework中的定義了一些類和相關(guān)協(xié)議,它最主要的作用就是為程序提供文字排版和渲染的功能。在程序中,通過Text Kit可以對文字進(jìn)行存儲(store)、布局(lay out),以及用最精細(xì)的排版方式(例如文字間距、換行和對齊等)來顯示文本內(nèi)容。 蘋果引入Text Kit的目的并非要取代已有的Core Text,Core Text的主要作用也是用于文字的排版和渲染中,它是一種先進(jìn)而又處于底層技術(shù),如果我們需要將文本內(nèi)容直接渲染到圖形上下文(Graphics context)時,從性能和易用性來考慮,最佳方案就是使用Core Text。而如果我們直接利用蘋果提供的一些控件(例如UITextView、UILabel和UITextField等)對文字進(jìn)行排版,無疑就是借助于UIkit framework中Text Kit提供的API。

Text Kit架構(gòu)

下面,我們通過圖1(此圖來自WWDC2013 Session 210)來了解一下Text Kit的架構(gòu)。圖1是基于iOS 7繪制的,從圖中,我們可以看到Text Kit是基于Core Text構(gòu)建的,它通過Core Text與Core Graphics進(jìn)行交互。而UI控件(UILabel、UITextField和UITextView)則構(gòu)建于Text Kit之上,可見這些文本控件可以利用Text Kit提供的API來對文字進(jìn)行排版和渲染處理。 從圖中我們也可以看到SDK提供的UIWebView是基于WebKit的,它不能使用Text Kit提供的功能。

圖1 Text Kit在iOS 7 SDK中的位置

我們再來看看圖1中的相關(guān)組件在iOS6里面是如何對應(yīng)的,如圖2所示,可以看出在iOS 6中是沒有Text Kit,并且UILabel、UIText和UITextView是基于String Drawing和WebKit構(gòu)建的。其中String Drawing是與Core Graphics直接通訊。

圖2 在iOS 6中并沒有Text Kit

Text Kit特點(diǎn)

從上面的介紹中,我們可以了解到Text Kit在UIKit中的作用非常重要。Text Kit在實(shí)際開發(fā)中具有如下特點(diǎn):

1.在UI控件中Text Kit完全掌控著文字的排版和渲染

2.UITextView、UITextField和UILabel是構(gòu)建于Text Kit之上的

3.能夠與動畫、UICollectionView和UITableView做到無縫集成

4.Text Kit具有這樣一些能力:Subclassing、Delegation和Notifcation。

Text Kit功能概述

下面我們看看通過Text Kit,都能實(shí)現(xiàn)那些功能(這里列出了是一些常用和重要功能):

1.對文字進(jìn)行分頁或多列排版

2.支持文字的換行、折疊和著色等處理

3.可以調(diào)整字與字之間的距離、行間距、文字大小、指定特定的字體

4.支持富文本編輯,可以自定義文字截斷

5.支持凸版印刷效果(letterpress)

6.支持?jǐn)?shù)據(jù)類型的檢測(例如鏈接、附件等)

如圖3,是利用Text Kit對文字做的分頁排版

圖3 利用Text Kit做的分頁排版效果

再看圖4,是利用Text Kit做的換行處理,其中對某個路徑范圍做了排除。

圖4 利用Text Kit做的換行處理效果

再來看看利用Text Kit做的凸版印刷效果,如圖5所示

圖5 利用Text Kit做的凸版印刷效果

Text Kit中重要的一些對象

下面我們來看看Text Kit中重要的幾個對象。

圖6 Text Kit中重要的幾個對象

如圖6所示,Text Kit中主要有4個重要的對象。

1.Text View是用來顯示文本內(nèi)容的控件,主要包括UILabel、UITextView和UITextField。

2.Text containers對應(yīng)著NSTextContainer類。NSTextContainer定義了文本可以排版的區(qū)域。一般來說,都是矩形區(qū)域,當(dāng)然,也可以根據(jù)需求,通過子類化NSTextContainer來創(chuàng)建別的一些形狀,例如圓形、不規(guī)則的形狀等。NSTextContainer不僅可以創(chuàng)建文本可以填充的區(qū)域,它還維護(hù)著一個數(shù)組——該數(shù)組定義了一個區(qū)域,排版的時候文字不會填充該區(qū)域,因此,我們可以在排版文字的時候,填充非文本元素(例如圖片,如圖4所示)。

3.Layout manager對應(yīng)著NSLayoutManager類。該類負(fù)責(zé)對文字進(jìn)行編輯排版處理——通過將存儲在NSTextStorage中的數(shù)據(jù)轉(zhuǎn)換為可以在視圖控件中顯示的文本內(nèi)容,并把統(tǒng)一的字符編碼映射到對應(yīng)的字形(glyphs)上,然后將字形排版到NSTextContainer定義的區(qū)域中。

4.Text storage對應(yīng)著NSTextStorage類。該類定義了Text Kit擴(kuò)展文本處理系統(tǒng)中的基本存儲機(jī)制。NSTextStorage繼承自NSmutableAttributedString,主要用來存儲文本的字符和相關(guān)屬性。另外,當(dāng)NSTextStorage中的字符或?qū)傩园l(fā)生了改變,會通知NSLayoutManager,進(jìn)而做到文本內(nèi)容的顯示更新。

通常情況下,NSTextStorage、NSLayoutManager和NSTextContainer是一一對應(yīng)的。如圖7所示關(guān)系:

圖7 普通排版

當(dāng)然,如果需要將文字顯示為多列,或多頁,可以按照如圖8所示關(guān)系——使用多個NSTextContainer。

圖8 多頁或者多列排版

如果針對不同的排版方式,則可以使用多個NSLayoutManager,如圖9所示

圖9 不同的排版方式

如圖10所示,通過形象的方式,對UITextView的組成做了分解。通常,我們在設(shè)備上只能看到最右邊的文本顯示界面,而內(nèi)部的NSTextStorage、NSLayoutManager和NSTextContainer是看不出來的。通常由NSLayoutManager從NSTextStorage中讀取出文本數(shù)據(jù),然后根據(jù)一定的排版方式,將文本排版到NSTextContainer中,再由NSTextContainer結(jié)合UITextView將最終效果顯示出來。

圖10 UITextView的分解

Text Kit示例

前面對Text Kit做了一些介紹,下面我們配合一個例子(圖文排版),來進(jìn)一步加深對Text Kit的認(rèn)識。具體實(shí)現(xiàn)步驟如下:

1.打開Xcode 5,新建一個Single View Application模板的程序,將工程命名為ExclusionPath。

2.打開Main.storyboard文件,然后再默認(rèn)View Controller的View里面分別添加一個UITextView和UIImageView。并將這兩個控件連接到ViewController.h中(名稱分別為textView何imageView)。然后給textView設(shè)置一些字符串,imageView設(shè)置一個圖片。

3.打開ViewController.m文件,找到viewDidLoad方法,用如下代碼替換該方法:

-?(void)viewDidLoad

{

[superviewDidLoad];

//創(chuàng)建一個平移手勢對象,該對象可以調(diào)用imagePanned:方法

UIPanGestureRecognizer?*panGes?=?[[UIPanGestureRecognizer?alloc]?initWithTarget:self?action:@selector(imagePanned:)];

[self.imageView?addGestureRecognizer:panGes];

self.textView.textContainer.exclusionPaths?=?@[[self?translatedBezierPath]];

}

在上面的代碼中,給imageView添加了一個平移手勢。另外通過調(diào)用translatedBezierPath方法,給textView的textContainer設(shè)置exclusionPaths屬性值。表示需要排除的區(qū)域(也就是圖片在排版中顯示的位置)。

下面來看一下translatedBezierPath方法的實(shí)現(xiàn),如下代碼所示

-?(UIBezierPath?*)translatedBezierPath

{

CGRect?butterflyImageRect?=?[self.textView?convertRect:self.imageView.frame?fromView:self.view];

UIBezierPath?*newButterflyPath?=?[UIBezierPath?bezierPathWithRect:butterflyImageRect];

returnnewButterflyPath;

}

在上面的代碼中,利用imageView的frame屬性創(chuàng)建了一個UIBezierPath,然后將該值返回。 5. 還記得第3步中創(chuàng)建的平移手勢嗎。里面有一個action需要實(shí)現(xiàn)imagePanned:,下面來看看這個方法的實(shí)現(xiàn):

-?(void)imagePanned:(id)sender

{

if([sender?isKindOfClass:[UIPanGestureRecognizerclass]])?{

UIPanGestureRecognizer?*localSender?=?sender;

if(localSender.state?==?UIGestureRecognizerStateBegan)?{

self.gestureStartingPoint?=?[localSender?translationInView:self.textView];

self.gestureStartingCenter?=?self.imageView.center;

}elseif(localSender.state?==?UIGestureRecognizerStateChanged)?{

CGPoint?currentPoint?=?[localSender?translationInView:self.textView];

CGFloat?distanceX?=?currentPoint.x?-?self.gestureStartingPoint.x;

CGFloat?distanceY?=?currentPoint.y?-?self.gestureStartingPoint.y;

CGPoint?newCenter?=?self.gestureStartingCenter;

newCenter.x?+=?distanceX;

newCenter.y?+=?distanceY;

self.imageView.center?=?newCenter;

self.textView.textContainer.exclusionPaths?=?@[[self?translatedBezierPath]];

}elseif(localSender.state?==?UIGestureRecognizerStateEnded)?{

self.gestureStartingPoint?=?CGPointZero;

self.gestureStartingCenter?=?CGPointZero;

}

}

}

在上面的代碼中首先根據(jù)平移的距離來設(shè)置imageView的位置,然后利用translatedBezierPath方法重新計算了一下排除區(qū)域。 6. 至此代碼編寫完畢,下面來運(yùn)行程序,看看實(shí)際效果。如圖11所示:

圖11 運(yùn)行效果

點(diǎn)擊下圖,下載代碼

小結(jié)

實(shí)際上,上面的示例,只是揭秘了Text Kit功能的冰山一角。從iOS7及以后的版本中,Text Kit在UIKit framework里面占據(jù)重要的地位,Text Kit在文字處理方面,具有非常強(qiáng)大的功能,并且開發(fā)者可以對Text Kit進(jìn)行定制和擴(kuò)展。據(jù)悉,蘋果利用了2年的時間來開發(fā)Text Kit,相信這對許多開發(fā)者來說都是福音。

推薦Text Kit學(xué)習(xí)資源

更多關(guān)于Text Kit的學(xué)習(xí)資料,請參考下面的內(nèi)容:

wwdc視頻:

Introducing Text Kit

Advanced Text Layouts and Effects with Text Kit

Using Fonts with Text Kit

蘋果官方參考文檔

Text Programming Guide for iOS.pdf

NSLayoutManager Class Reference for iOS.pdf

NSLayoutManagerDelegate Protocol Reference for iOS.pdf

NSTextContainer Class Reference for iOS.pdf

NSTextStorage Class Reference for iOS.pdf

NSTextStorageDelegate Protocol Reference for iOS.pdf

蘋果官方示例:

IntroToTextKit

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容