上一節(jié)主要介紹曾經(jīng)走過的彎路和設(shè)計(jì)思路,這一節(jié)我將介紹具體的實(shí)現(xiàn)方案 和 TextKit 缴淋。為什么要介紹 TextKit 呢?為了讓富文本處理的相關(guān)代碼和 UITextView 分離昼蛀,以達(dá)到高內(nèi)聚宴猾、低耦合。如果直接在 UITextView 中定義一個(gè)類型為 NSMutableAttributedString 的參數(shù)叼旋,也可以實(shí)現(xiàn)相關(guān)功能,但是這樣富文本處理的代碼和 textView 邏輯處理的代碼就冗合在一起沦辙,可讀性和可維護(hù)性比較差夫植。
TextKit
何為 TextKit ,它是由蘋果官方提供提供油讯、功能強(qiáng)大的內(nèi)容管理框架详民,它可以控制 UITextView 內(nèi)容顯示樣式、布局陌兑、行間距等等很多細(xì)節(jié)沈跨。 TextKit 主要包括三個(gè)類,分別為:NSLayoutManager兔综、NSTextContainer饿凛、NSTextStorage。
- NSLayoutManager 控制布局软驰;
- NSTextContainer 控制顯示的區(qū)域和不可以顯示的區(qū)域涧窒;
- NSTextStorage 內(nèi)容管理器。
蘋果在 GitHub 上提供了 TextKit 的簡單使用和功能演示锭亏。想更進(jìn)一步了解請查閱蘋果官方文檔纠吴,鏈接我貼在下面。
A little demo application showing off some features of the new TextKit classes in iOS 7
TextKit
TextKit is a full-featured, high-level set of classes for handling text and fine typography. Using TextKit, you can lay out styled text into paragraphs, columns, and pages; you can flow text around arbitrary regions such as graphics; and you can use it to manage multiple fonts. If you were considering using Core Text to implement text rendering, you should consider TextKit instead. TextKit is integrated with all UIKit text-based controls to enable apps to create, edit, display, and store text more easily—and with less code than was previously possible in iOS.
TextKit comprises new UIKit classes, along with extensions to existing classes, including the following:
The NSAttributedString class has been extended to support new attributes.
The NSLayoutManager class generates glyphs and lays out text.
The NSTextContainer class defines a region where text is laid out.
The NSTextStorage class defines the fundamental interface for managing text-based content.
For more information about TextKit, see Text Programming Guide for iOS.
NSTextStorage
TextKit 三劍客中慧瘤,使用 NSTextStorage 便可實(shí)現(xiàn)功能戴已。首先將 RichTextStorage (繼承自 NSTextStorage)和 UITextView 綁定,讓其將內(nèi)容管理交于我們定義的內(nèi)容管理器管理锅减。
- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer
{
if (textContainer == nil) {
textContainer = [[NSTextContainer alloc] init];
}
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[layoutManager addTextContainer:textContainer];
_richTextStorage = [[RichTextStorage alloc] init];
[_richTextStorage addLayoutManager:layoutManager];
self = [super initWithFrame:frame textContainer:textContainer];
if (self) {
}
return self;
}
RichTextStorage 必須覆寫以下方法糖儡。
- (NSString *)string;
- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range;
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str;
- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range;
- 第一個(gè)方法是返回當(dāng)前內(nèi)容管理器中管理的文本,換句話說就是顯示到 textView 上的文本上煤;
- 第二個(gè)方法返回指定位置的字符的屬性信息休玩;
- 第三個(gè)方法使用字符串替換指定范圍內(nèi)的文本;
- 第四個(gè)方法設(shè)置內(nèi)容管理器中文本的屬性信息。
處理富文本的代碼在第三個(gè)方法中進(jìn)行拴疤,其他三個(gè)方法按照官方范例覆寫便可永部。官方給出的案例定義了一個(gè)變量 _imp,主要控制顯示到 textView 上的內(nèi)容呐矾,這里我們需要再定義一個(gè)變量 _richImp 用于存放富文本的相關(guān)信息苔埋。_imp 和 _richImp 的關(guān)系很特殊,他們的 string 的值是相同的蜒犯,字符的改變也是同步组橄。不同點(diǎn)在于,_imp 中的屬性只與顯示出來的內(nèi)容相關(guān)罚随,_richImp 中存與富文本信息相關(guān)的內(nèi)容玉工。
富文本處理
在方法 replaceCharactersInRange:withString: 中傳入文本
“你好<a title=‘title’ id=‘001’ type=‘user’ >”
處理后的文本為
你好title @{@"title":@"title", @"id":@"001", @"type" :@"user" };
我們將處理后帶格式的文本更新到 _richImp,不帶格式的更新到 _imp。
這個(gè)時(shí)候你可能會(huì)問淘菩,顯示到 textView 的富文本需要顯示不同的顏色遵班,如何實(shí)現(xiàn)呢?
富文本顯示樣式的控制
在方法 processEditing 中控制富文本的字體和顏色潮改。
我們用 _richImp 調(diào)用 enumerateAttributesInRange: options:usingBlock: 方法狭郑,獲取到的 attrs 便是在上面設(shè)置的字典@[title=title;id=001;type=user] ,range 是該字典的范圍汇在。從中取出 type 的值翰萨,根據(jù) type 的值指定更新 _imp 中的字體和顏色屬性。比如 user 設(shè)置為紅色 15 號字體糕殉,標(biāo)簽設(shè)置為 藍(lán)色 12號字體亩鬼。
- (void)processEditing
{
NSRange paragaphRange = [self.string paragraphRangeForRange: self.editedRange];
// 更新顏色(枚舉 _richText)
NSMutableAttributedString *newRichText = [[NSMutableAttributedString alloc] initWithAttributedString:_richImp];
[newRichText enumerateAttributesInRange:paragaphRange options:NSAttributedStringEnumerationReverse usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
NSString *type = [attrs valueForKey:@"type"];
if ([type.lowercaseString isEqualToString:@"user"]) {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:color forKey:NSForegroundColorAttributeName];
[dict setValue:_font forKey:NSFontAttributeName];
[self addAttributes:dict range:range];
}
}];
[super processEditing]; // 最后調(diào)用
}
到這里我們基本上完成了富文本的輸入和顯示,但是還存在很多問題還沒有解決糙麦,我這里列舉一下:
光標(biāo)的位置辛孵,現(xiàn)在如果你移動(dòng)光標(biāo),光標(biāo)是可以移動(dòng)到富文本中的赡磅。當(dāng)然如果你有這種需求魄缚,我們就另當(dāng)別論了。
-
選擇的文本只包含了富文本的部分內(nèi)容焚廊,前半部分冶匹,或者后半部分。
今天先說到這兒咆瘟,如果你有興趣嚼隘,希望親自實(shí)現(xiàn)一下。我將在下一節(jié)中袒餐,解決這些問題飞蛹,并做一些優(yōu)化谤狡。歡迎各位掃碼加入圈子 【猿圈】,在這里我們一起聊技術(shù)卧檐、聊 bug墓懂、聊那些年我們踩過的坑。