Text Kit學習(入門和進階): ?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.reibang.com/p/2f72a5fa99f1
在iOS 7之前,應(yīng)用中字體的大小用戶是不能設(shè)置的氧吐,而且開發(fā)人員要想實現(xiàn)多種樣式的文字排版是件非常麻煩的事情域携。在iOS 7之后,這些問題都解決了镣衡,Text Kit就是解決這些問題的鑰匙暴拄。本章將向大家介紹iOS 7中文字排版和渲染引擎——Text Kit肋拔。
9.1 Text Kit基礎(chǔ)
Text Kit最主要的作用就是為程序提供文字排版和渲染的功能。通過Text Kit可以對文字進行存儲纬纪、布局蚓再,以更加精準的排版方式來顯示文本內(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是最好的選擇卵皂,因為它能夠直接使用UIKit提供的一些文本控件秩铆,例如:UITextView、UILabel和UITextField,對文字進行排版殴玛。
Text Kit具有很多優(yōu)點:文本控件UITextView捅膘、UITextField和UILabel是構(gòu)建于Text Kit之上的。Text Kit完全掌控著文字的排版和渲染:可以調(diào)整字距滚粟、行距寻仗、文字大小,指定特定的字體凡壤,對文字進行分頁或分欄署尤,支持富文本編輯、自定義文字截斷亚侠,支持文字的換行曹体、折疊和著色等處理,支持凸版印刷效果盖奈。
9.1.2 Text Kit架構(gòu)
在開始介紹Text Kit API之前混坞,我們有必要理解一下iOS的文字渲染框架。從圖9-1可見钢坦,Text Kit是基于Core Text構(gòu)建的,它通過Core Text與Core Graphics進行交互啥酱。而文本控件爹凹,如:UILabel、UITextField和UITextView镶殷,則構(gòu)建于Text Kit之上禾酱,可見這些文本控件可以利用Text Kit提供的API來對文字進行排版和渲染處理。從圖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之前文本控件也可以實現(xiàn)多種樣式的文字排版绽族,但是事實上是通過WebKit實現(xiàn)的姨涡。WebKit是一種瀏覽器內(nèi)核技術(shù),使用它進行文字渲染會消耗掉比較多的內(nèi)存吧慢,對應(yīng)用的性能有一定的影響涛漂。
圖9-2 iOS 7之前的文字渲染
9.1.3 Text Kit中的核心類
我們在使用Text Kit時,會涉及如下核心類检诗。
NSTextContainer匈仗。定義了文本可以排版的區(qū)域底哗。默認情況下是矩形區(qū)域,如果是其他形狀的區(qū)域锚沸,需要通過子類化NSTextContainer來創(chuàng)建跋选。
NSLayoutManager。該類負責對文字進行編輯排版處理哗蜈,將存儲在NSTextStorage中的數(shù)據(jù)轉(zhuǎn)換為可以在視圖控件中顯示的文本內(nèi)容前标,并把字符編碼映射到對應(yīng)的字形上,然后將字形排版到NSTextContainer定義的區(qū)域中距潘。
NSTextStorage炼列。主要用來存儲文本的字符和相關(guān)屬性,是NSMutableAttributedString的子類(見圖9-3)音比。此外俭尖,當NSTextStorage中的字符或?qū)傩园l(fā)生改變時,會通知NSLayoutManager洞翩,進而做到文本內(nèi)容的顯示更新稽犁。
NSAttributedString。支持渲染不同風格的文本骚亿。
NSMutableAttributedString已亥。可變類型的NSAttributedString来屠,是NSAttributedString的子類(見圖9-3)虑椎。
圖9-3 NSAttributedString類圖
NSLayoutManager、NSTextContainer俱笛、NSTextStorage之間究竟是什么關(guān)系呢捆姜?圖9-4所示文本控件通過它們實現(xiàn)了顯示文本內(nèi)容到屏幕上的過程。NSLayoutManager對象從NSTextStorage對象中取得文本內(nèi)容迎膜,進行排版泥技,然后把排版之后的文本放到NSTextContainer對象指定的區(qū)域上。最后再由一個文本控件從NSTextContainer中取出內(nèi)容顯示到屏幕中星虹。
圖9-4 NSLayoutManager零抬、NSTextContainer和NSTextStorage之間的關(guān)系
NSLayoutManager對象起到承上啟下的作用。還記得鉛字排版嗎宽涌?在沒有計算機排版的時代平夜,排版工人都是通過這種方法實現(xiàn)的,他們從鉛字庫中找到特定字體的字母卸亮,然后把它放到活動字模中(見圖9-5)忽妒,最后進行印刷。這個過程可以很好地幫助我們理解NSLayoutManager、NSTextContainer和NSTextStorage之間的關(guān)系段直,其中NSLayoutManager對象相當于排版工人吃溅,NSTextStorage對象相當于特定字體的鉛字庫,而NSTextContainer對象就相當于我們看到的活動字模鸯檬。文本控件從NSTextContainer中取出內(nèi)容顯示到屏幕的過程就相當于印刷的過程决侈。
圖9-5 鉛字排版1
1該圖出自維基百科http://zh.wikipedia.org/wiki/File:MetalTypeZoomIn.JPG。
9.1.4 實例:凸版印刷效果
為了更好地理解我們前面介紹的API內(nèi)容喧务,下面我們通過一個實例介紹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è)置某些單詞樣式風格。
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];⑦_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ù)能夠指定一個中心點,后面的兩個參數(shù)沿著self.view.bounds區(qū)域向內(nèi)縮進量鲸睛。這樣可以使得文字部分不會太靠近視圖的邊界娜饵。
第②行代碼是創(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控件,并且重新添加到視圖上寂呛。這主要是因為只有重新創(chuàng)建代碼才能通過Text Kit中NSLayoutManager來管理怎诫,而原來在Interface Builder中創(chuàng)建的TextView控件不再使用了。
第⑩~?行代碼是實現(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:方法實現(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)建正則表達式NSRegularExpression對象,其中的regularExpressionWithPattern參數(shù)指定正則表達式缸剪。第②行代碼通過正則表達式NSRegularExpression對象對TextView中的文本內(nèi)容進行掃描吗铐,結(jié)果放到數(shù)組中。第③行代碼從集合中取出NSTextCheckingResult結(jié)果對象杏节。第④行代碼是為找到的文本設(shè)置顏色為紅色風格唬渗。
編碼完成之后我們就可以運行一下看看效果了,如圖9-7所示奋渔,其中的“我”和“I”是紅色顯示的镊逝,整個的文字設(shè)置為凸版印刷效果。圖9-7 運行效果
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)繞路徑實現(xiàn)文字圖片混合排版巡莹。我們可以在上一節(jié)的案例基礎(chǔ)上修改司志,打開Main_iPhone.storyboard故事板文件,從對象庫中拖曳ImageView控件到設(shè)計視圖上降宅,如圖9-9所示骂远,通過設(shè)置Image屬性設(shè)置要顯示的圖片為MetalType.png躺酒,當然我們之前需要將圖片導(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ù)學家貝塞爾在工作中發(fā)現(xiàn)檩淋,任何一條曲線都可以通過與它相切的控制線兩端的點的位置來定義。因此萄金,貝賽爾曲線可以用4個點描述蟀悦,其中兩個點描述兩個端點,另外兩個描述每一端的切線氧敢。貝賽爾曲線可以分為:二次方貝賽爾曲線和高階貝賽爾曲線熬芜。
獲得ImageView的貝塞爾曲線路徑是通過第③行代碼的translatedBezierPath方法實現(xiàn)的。代碼中的第④行是進行坐標系轉(zhuǎn)換福稳,如圖9-10所示,原來ImageView的坐標系是相對于View的坐標(62, 72)瑞侮,通過convertRect: fromView:方法將坐標系轉(zhuǎn)換為相對于TextView的坐標(52, 52)的圆。這是因為我們需要獲得的是TextView的文字圍繞ImageView路徑,因此坐標系需要參照TextView半火。圖9-10 坐標系的轉(zhuǎ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è)置中進行設(shè)置就萬事大吉了且叁,我們還要在應(yīng)用代碼中進行編程都哭,以應(yīng)對這些變化。我們需要在應(yīng)用中給文本控件設(shè)置為用戶設(shè)置的字體逞带,而不是在代碼中硬編碼字體及大小欺矫。iOS 7中可以通過UIFont中新增的preferredFontForTextStyle:方法來獲取用戶設(shè)置的字體。
iOS 7中提供了6種字體樣式供選擇展氓。
UIFontTextStyleHeadline穆趴。標題字體,例如:報紙的標題遇汞。
UIFontTextStyleSubheadline未妹。子標題字體。
UIFontTextStyleBody勺疼。正文字體教寂。
UIFontTextStyleFootnote。腳注字體执庐。
UIFontTextStyleCaption1酪耕。標題字體,一般用于照片或者字幕轨淌。
UIFontTextStyleCaption2迂烁。另一個可選Caption字體。
這6種字體具體樣式可見圖9-13所示递鹉。
圖9-13 iOS系統(tǒng)提供的6種字體樣式
處理系統(tǒng)提供了6種樣式的字體盟步,我們還可以自己定義字體。
當用戶在設(shè)置中改變了字體躏结,系統(tǒng)會給應(yīng)用程序發(fā)送UIContentSizeCategoryDidChangeNotification通知却盘,我們需要監(jiān)聽這個通知,并通過下面的代碼重新設(shè)置文本控件字體即可媳拴。
self.textView.font=[UIFontpreferredFontForTextStyle:UIFontTextStyleBody];
為了能夠更好地理解動態(tài)字體黄橘,下面我們通過一個實例介紹一下。我們對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通知,當系統(tǒng)發(fā)出這個通知后子巾,會回調(diào)preferredContentSizeChanged:方法帆赢。代碼第②行所定義的就是preferredContentSizeChanged:回調(diào)方法小压。在這個方法中我們通過第③行代碼實現(xiàn)重新設(shè)置TextView的字體樣式。
編碼完成之后我們就可以運行一下看看效果了椰于,如圖9-14所示是運行之后通過系統(tǒng)設(shè)置改變文字大小前后的對比怠益。
圖9-14 改變文字大小前后
在這個案例基礎(chǔ)上大家可以改變不同的字體風格看看運行的效果。
9.4 小結(jié)
在本章中廉羔,我們首先介紹了iOS 7的Text Kit技術(shù)溉痢,通過Text Kit技術(shù)我們實現(xiàn)了文本圖片混合排版,動態(tài)字體設(shè)置等憋他。
轉(zhuǎn)自TracyYih的博客
更詳細的內(nèi)容可以參考官方文檔 《Text Programming Guide for iOS》孩饼。
“Text Kit指的是UIKit框架中用于提供高質(zhì)量排版服務(wù)的一些類和協(xié)議,它讓程序能夠存儲竹挡,排版和顯示文本信息镀娶,并支持排版所需要的所有特性,包括字距調(diào)整揪罕、連寫梯码、換行和對齊等『脝”
以前轩娶,如果我們想實現(xiàn)復(fù)雜的文本排版,例如在textView中顯示不同樣式的文本框往,或者圖片和文字混排鳄抒,你可能就需要借助于UIWebView或者深入研究一下Core Text。在iOS6中椰弊,UILabel许溅、UITextField、UITextView增加了一個NSAttributedString屬性秉版,可以稍微解決一些排版問題贤重,但是支持的力度還不夠。現(xiàn)在Text Kit完全改變了這種現(xiàn)狀清焕。
Text Kit是基于Core Text構(gòu)建的快速并蝗、先進的文本排版和渲染引擎,并且與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對象侨核,當它的字符或者屬性改變時會通知到自己所管理的layout Manager對象以便它們作出相應(yīng)的反應(yīng)草穆。在layout manager上面是一個NSTextContainer對象,用于為layout manager定義坐標系和一些幾何特性搓译。例如悲柱,如果你想UITextView中的文本環(huán)繞在一張圖片四周,你可以給text container設(shè)定一個排除路徑(exclusion path)些己。
UIBezierPath?*exclusion?=?ButterflyBezierPath;
self.textView.textContainer.exclusionPaths?=?@[exclusion];
Text container能夠處理擊中測試(hit tests)豌鸡,所以可以定位到點擊的字符在文本中的位置。此外它還提供一些代理方法讓開發(fā)者能夠自己定義鏈接點擊后的處理事件段标。
通過基于Text Kit重新構(gòu)建UILabel涯冠、UITextField和UITextView,蘋果給開發(fā)者更大的靈活性和能力來設(shè)計富文本視圖逼庞,同時簡化了這些控件的使用蛇更,因為它們是以同樣的方式設(shè)計的,所有這些好處都是站在巨人(Core Text)的肩上往堡。通常更強大的功能和靈活性也就意味著需要更多的設(shè)置和管理械荷,但是,如果你只是想顯示一段簡單的文本虑灰,你還是可以像以前一樣使用吨瞎。
self.textLabel.text?=?@"Hello?Text?Kit";
本文翻譯自《iOS 7: Text Kit》
Text Kit進階
上一篇文章Text Kit入門中我們主要了解了什么是Text Kit及它的一些架構(gòu)和基本特性,這篇文章中會涉及關(guān)于Text Kit的更多具體應(yīng)用穆咐。
Text Kit是建立在Core Text框架上的颤诀,我們知道CoreText.framework是一個龐大而復(fù)雜的框架,而Text Kit在繼承了Core Text強大功能的同時給開發(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è)定的字體大小和粗細來顯示文本內(nèi)容。
分別在設(shè)置\通用\輔助功能和設(shè)置\通用\文字大小中可以設(shè)置文本在應(yīng)用程序中顯示的粗細和大小拍柒。
iOS7對系統(tǒng)字體在顯示上做了一些優(yōu)化心傀,讓不同大小的字體在屏幕上都能清晰的顯示。通常用戶設(shè)置了自己偏好的字體拆讯,他們希望在所有程序中都看到文本顯示是根據(jù)他們的設(shè)定進行調(diào)整脂男。為了實現(xiàn)這個养叛,開發(fā)者需要在自己的應(yīng)用中給文本控件設(shè)置當前用戶設(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è)定的字體大小及粗細顯示瓶珊,但是如果程序在運行時,用戶切換到設(shè)置里修改了字體眶明,這是在切回程序艰毒,字體并不會自動跟著變。這時就需要我們自己來更新一下控件的字體了搜囱。
在系統(tǒng)字體修改時丑瞧,系統(tǒng)會給運行中的程序發(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];
}
當然,有的時候要適應(yīng)動態(tài)修改的字體并不是這么設(shè)置一下就完事了扮宠,控件的大小可能也需要進行相應(yīng)的調(diào)整西乖,這時我們程序中的控件大小也不應(yīng)該寫死,而是需要根據(jù)字體大小來計算.
凸版印刷體效果(Letterpress effects)
凸版印刷替效果是給文字加上奇妙陰影和高光坛增,讓文字看起有凹凸感获雕,像是被壓在屏幕上。當然這種看起來很高端大氣上檔次的效果實現(xiàn)起來確實相當?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];
所以實現(xiàn)Exclusion paths的主要工作就是獲取這個path。
動態(tài)文本格式化和存儲(Dynamic text formatting and storage)
好了豆拨,到現(xiàn)在我們知道了Text Kit可以動態(tài)的根據(jù)用戶設(shè)置的字體大小進行調(diào)整直奋,但是如果具體某個文本顯示控件中的文本樣式能夠動態(tài)調(diào)整是不是會更酷一些呢?
實現(xiàn)這些才是真正體現(xiàn)Text Kit強大之處的時候施禾,在此之前你需要理解Text Kit中的文本存儲系統(tǒng)是怎么工作的,下圖顯示了Text Kit中文本的保存搁胆、渲染和現(xiàn)實之間的關(guān)系弥搞。
當你使用UITextView、UILabel渠旁、UITextField控件的時候攀例,系統(tǒng)會自動創(chuàng)建上面這些類,你可以選擇直接使用這么默認的實現(xiàn)或者為你的控件自定義這幾個中的任何一個顾腊。
1.NSTextStorage本身繼承與NSMutableAttributedString粤铭,它是以attributed string的形式保存需要渲染的文本,并在文本內(nèi)容改變的時候通知到對應(yīng)的layout manager對象杂靶。通常你需要創(chuàng)建NSTextStorage的子類來在文本改變時進行文本顯示樣式的更新梆惯。
2.NSLayoutManager作為文本控件中的排版引擎接收保存的文本并在屏幕上渲染出來。
3.NSTextContainer描述了文本在屏幕上顯示時的幾何區(qū)域吗垮,每個text container與一個具體的UITextView相關(guān)聯(lián)垛吗。如果你需要定義一個很復(fù)雜形狀的區(qū)域來顯示文本,你可能需要創(chuàng)建NSTextContainer子類烁登。
要實現(xiàn)我們上面描述的動態(tài)文本格式化功能怯屉,我們需要創(chuàng)建NSTextStorage子類以便在用戶輸入文本的時候動態(tài)的增加文本屬性。自定義了text storage后饵沧,我們需要替換調(diào)UITextView默認的text storage锨络。
創(chuàng)建NSTextStorage的子類
我們創(chuàng)建NSTextStorage子類,命名為MarkupTextStorage狼牺,在實現(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需要寫不少的代碼,因為NSTextStorage是一個類集群中的一個開發(fā)接口咏瑟,不能只是繼承它然后重載很少的方法來拓展它的功能拂到,而是需要自己實現(xiàn)很多細節(jié)。
類集群(Class cluster)是蘋果Cocoa(Touch)框架中常用的設(shè)計模式之一码泞。
類集群是Objective-C中對抽象工廠模式的簡單實現(xiàn)兄旬,為創(chuàng)建一些列相關(guān)或獨立對象提供了統(tǒng)一的接口而不用指定具體的類。常用的像NSArray和NSNumber事實上也是一系列類集群的開放接口。
蘋果使用類集群是為了將一些類具體類隱藏在開放的抽象父類之下领铐,外面通過抽象父類的方法來創(chuàng)建私有子類的實例悯森,并且外界也完全不知道工廠分配到了哪個私有類,因為它們始終只和開放接口交互绪撵。
使用類集群確實簡化了接口瓢姻,讓類更容易被使用,但是要知道魚和熊掌不可兼得音诈,你又想簡單又想可拓展性強幻碱,哪有那么好的事啊细溅?所以創(chuàng)建一個類集群中的抽象父類就沒有那么簡單了褥傍。
好了,上面解釋了這么多其實主要就說明了為什么子類化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];
}
這個方法用于擴大文本匹配的范圍党远,因為changedRange只是標識出一個字符削解,lineRangeForRange會將范圍擴大到當前的一整行。
下面就剩下匹配特定格式的文本來顯示對應(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沟娱,通過正則表達式來給特定格式的字符串設(shè)定特定顯示樣式氛驮,形成一個對應(yīng)的字典。然后在applyStylesToRange:中利用已定義好的樣式字典來給匹配的文本端增加樣式济似。
到這里本篇文章的內(nèi)容就結(jié)束了矫废,其實前面三點都很簡單,稍微過一下就能用砰蠢。最后一個動態(tài)文本格式化內(nèi)容稍微多一點蓖扑,可以結(jié)合我的代碼TextKitDemo來看。
參考鏈接:
http://www.raywenderlich.com/50151/text-kit-tutorial
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特點
Text Kit功能概述
Text Kit中重要的一些對象
Text Kit示例
小結(jié)
推薦Text Kit學習資源
什么是Text Kit
在iOS7中,蘋果引入了Text Kit--Text Kit是一個快速而又現(xiàn)代化的文字排版和渲染引擎。Text Kit在UIKit framework中的定義了一些類和相關(guān)協(xié)議柜去,它最主要的作用就是為程序提供文字排版和渲染的功能灰嫉。在程序中,通過Text Kit可以對文字進行存儲(store)嗓奢、布局(lay out)讼撒,以及用最精細的排版方式(例如文字間距、換行和對齊等)來顯示文本內(nèi)容股耽。 蘋果引入Text Kit的目的并非要取代已有的Core Text椿肩,Core Text的主要作用也是用于文字的排版和渲染中,它是一種先進而又處于底層技術(shù)豺谈,如果我們需要將文本內(nèi)容直接渲染到圖形上下文(Graphics context)時,從性能和易用性來考慮贡这,最佳方案就是使用Core Text茬末。而如果我們直接利用蘋果提供的一些控件(例如UITextView、UILabel和UITextField等)對文字進行排版盖矫,無疑就是借助于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進行交互换衬。而UI控件(UILabel、UITextField和UITextView)則構(gòu)建于Text Kit之上证芭,可見這些文本控件可以利用Text Kit提供的API來對文字進行排版和渲染處理瞳浦。 從圖中我們也可以看到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特點
從上面的介紹中傻咖,我們可以了解到Text Kit在UIKit中的作用非常重要。Text Kit在實際開發(fā)中具有如下特點:
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硬纤,都能實現(xiàn)那些功能(這里列出了是一些常用和重要功能):
1.對文字進行分頁或多列排版
2.支持文字的換行解滓、折疊和著色等處理
3.可以調(diào)整字與字之間的距離、行間距筝家、文字大小洼裤、指定特定的字體
4.支持富文本編輯,可以自定義文字截斷
5.支持凸版印刷效果(letterpress)
6.支持數(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ū)域盆色,當然灰蛙,也可以根據(jù)需求,通過子類化NSTextContainer來創(chuàng)建別的一些形狀傅事,例如圓形缕允、不規(guī)則的形狀等。NSTextContainer不僅可以創(chuàng)建文本可以填充的區(qū)域蹭越,它還維護著一個數(shù)組——該數(shù)組定義了一個區(qū)域障本,排版的時候文字不會填充該區(qū)域,因此响鹃,我們可以在排版文字的時候驾霜,填充非文本元素(例如圖片,如圖4所示)买置。
3.Layout manager對應(yīng)著NSLayoutManager類粪糙。該類負責對文字進行編輯排版處理——通過將存儲在NSTextStorage中的數(shù)據(jù)轉(zhuǎn)換為可以在視圖控件中顯示的文本內(nèi)容,并把統(tǒng)一的字符編碼映射到對應(yīng)的字形(glyphs)上忿项,然后將字形排版到NSTextContainer定義的區(qū)域中懈叹。
4.Text storage對應(yīng)著NSTextStorage類。該類定義了Text Kit擴展文本處理系統(tǒng)中的基本存儲機制征候。NSTextStorage繼承自NSmutableAttributedString,主要用來存儲文本的字符和相關(guān)屬性家夺。另外,當NSTextStorage中的字符或?qū)傩园l(fā)生了改變伐弹,會通知NSLayoutManager拉馋,進而做到文本內(nèi)容的顯示更新。
通常情況下惨好,NSTextStorage煌茴、NSLayoutManager和NSTextContainer是一一對應(yīng)的。如圖7所示關(guān)系:
圖7 普通排版
當然日川,如果需要將文字顯示為多列蔓腐,或多頁,可以按照如圖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做了一些介紹奕翔,下面我們配合一個例子(圖文排版)裕寨,來進一步加深對Text Kit的認識。具體實現(xiàn)步驟如下:
1.打開Xcode 5派继,新建一個Single View Application模板的程序宾袜,將工程命名為ExclusionPath。
2.打開Main.storyboard文件驾窟,然后再默認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方法的實現(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需要實現(xiàn)imagePanned:,下面來看看這個方法的實現(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. 至此代碼編寫完畢,下面來運行程序铜涉,看看實際效果智玻。如圖11所示:
圖11 運行效果
點擊下圖,下載代碼
小結(jié)
實際上芙代,上面的示例吊奢,只是揭秘了Text Kit功能的冰山一角。從iOS7及以后的版本中纹烹,Text Kit在UIKit framework里面占據(jù)重要的地位页滚,Text Kit在文字處理方面,具有非常強大的功能铺呵,并且開發(fā)者可以對Text Kit進行定制和擴展裹驰。據(jù)悉,蘋果利用了2年的時間來開發(fā)Text Kit片挂,相信這對許多開發(fā)者來說都是福音幻林。
推薦Text Kit學習資源
更多關(guān)于Text Kit的學習資料,請參考下面的內(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