上一节主要介绍曾经走过的弯路和设计思路,这一节我将介绍具体的实现方案 和 TextKit 。为什么要介绍 TextKit 呢?为了让富文本处理的相关代码和 UITextView 分离,以达到高内聚、低耦合。如果直接在 UITextView 中定义一个类型为 NSMutableAttributedString 的参数,也可以实现相关功能,但是这样富文本处理的代码和 textView 逻辑处理的代码就冗合在一起,可读性和可维护性比较差。
9417D82C-DF2E-4169-A2BA-A3438F3D70A5.png
TextKit
何为 TextKit ,它是由苹果官方提供提供、功能强大的内容管理框架,它可以控制 UITextView 内容显示样式、布局、行间距等等很多细节。 TextKit 主要包括三个类,分别为:NSLayoutManager、NSTextContainer、NSTextStorage。
- NSLayoutManager 控制布局;
- NSTextContainer 控制显示的区域和不可以显示的区域;
- NSTextStorage 内容管理器。
苹果在 GitHub 上提供了 TextKit 的简单使用和功能演示。想更进一步了解请查阅苹果官方文档,链接我贴在下面。
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 便可实现功能。首先将 RichTextStorage (继承自 NSTextStorage)和 UITextView 绑定,让其将内容管理交于我们定义的内容管理器管理。
- (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;
- 第一个方法是返回当前内容管理器中管理的文本,换句话说就是显示到 textView 上的文本;
- 第二个方法返回指定位置的字符的属性信息;
- 第三个方法使用字符串替换指定范围内的文本;
- 第四个方法设置内容管理器中文本的属性信息。
处理富文本的代码在第三个方法中进行,其他三个方法按照官方范例覆写便可。官方给出的案例定义了一个变量 _imp,主要控制显示到 textView 上的内容,这里我们需要再定义一个变量 _richImp 用于存放富文本的相关信息。_imp 和 _richImp 的关系很特殊,他们的 string 的值是相同的,字符的改变也是同步。不同点在于,_imp 中的属性只与显示出来的内容相关,_richImp 中存与富文本信息相关的内容。
富文本处理
在方法 replaceCharactersInRange:withString: 中传入文本
“你好<a title=‘title’ id=‘001’ type=‘user’ >”
处理后的文本为
你好title @{@"title":@"title", @"id":@"001", @"type" :@"user" };
我们将处理后带格式的文本更新到 _richImp,不带格式的更新到 _imp。
这个时候你可能会问,显示到 textView 的富文本需要显示不同的颜色,如何实现呢?
富文本显示样式的控制
在方法 processEditing 中控制富文本的字体和颜色。
我们用 _richImp 调用 enumerateAttributesInRange: options:usingBlock: 方法,获取到的 attrs 便是在上面设置的字典@[title=title;id=001;type=user] ,range 是该字典的范围。从中取出 type 的值,根据 type 的值指定更新 _imp 中的字体和颜色属性。比如 user 设置为红色 15 号字体,标签设置为 蓝色 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]; // 最后调用
}
到这里我们基本上完成了富文本的输入和显示,但是还存在很多问题还没有解决,我这里列举一下:
-
光标的位置,现在如果你移动光标,光标是可以移动到富文本中的。当然如果你有这种需求,我们就另当别论了。
-
选择的文本只包含了富文本的部分内容,前半部分,或者后半部分。
今天先说到这儿,如果你有兴趣,希望亲自实现一下。我将在下一节中,解决这些问题,并做一些优化。欢迎各位扫码加入圈子 【猿圈】,在这里我们一起聊技术、聊 bug、聊那些年我们踩过的坑。
网友评论