一个简单富文本输入框控件的设计历程(二)

上一节主要介绍曾经走过的弯路和设计思路,这一节我将介绍具体的实现方案 和 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、聊那些年我们踩过的坑。

ECD23296A3C9AC33E9F6320A98A070E1.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容