在iOS开发中我们常常使用UIKit的UITextView、UITextField、UILabel来显示文字。它们底层都是基于一个叫做TextKit的强大引擎。通过TextKit,我们可以方便地修改文字的样式和排版,而不需要直接操作复杂的Core Text。

1.什么是TextKit

在iOS7中,苹果引入了Text Kit——一个快速而又现代化的文字排版和渲染引擎。Text Kit在UIKit framework中的定义了一些类和相关协议,它最主要的作用就是为程序提供文字排版和渲染的功能。通过Text Kit可以对文字进行存储(store)、布局(lay out),以及用最精细的排版方式(例如文字间距、换行和对齐等)来显示文本内容。
苹果引入Text Kit的目的并非要取代已有的Core Text,Core Text的主要作用也是用于文字的排版和渲染中,它是一种先进而又处于底层技术,如果我们需要将文本内容直接渲染到图形上下文(Graphics context)时,从性能和易用性来考虑,最佳方案就是使用Core Text。而如果我们直接利用苹果提供的一些控件(例如UITextView、UILabel和UITextField等)对文字进行排版,无疑就是借助于UIkit framework中Text Kit提供的API。

2.TextKit的作用

两个最重要的功能:

  1. 文字排版
  2. 文字渲染

3.TextKit中的类

要了解TextKit需要先了解其包含的几个类:

1.Text Storage(NSTextStorage):NSMutableAttributedString的子类,保存需要显示的文字和属性。
2.TextContainer(NSTextContainer):确定文字的布局区域。一般为矩形,但是可以创建NSTextContainer的子类来创建其它如圆形、五边形或不规则图形等。
3.Layout Manager(NSLayoutManager):负责根据NSTextContainer的布局信息渲染NSStorage中的文字。
4.TextView一般为UITextView等

TextKit是典型的MVC(model-view-controller )范例:

  1. Controller: NSLayoutManager。负责将NSTextStorage中的字符转换为文字符号,根据NSTextContainer对文字符号进行布局并显示到View中
  2. Model: NSTextStorage和NSTextContainer。前者保存有文字和对应的字体、颜色、大小等属性。后者保存了文字的绘制区域。
  3. View :UITextView或其它UIView的子类

一般情况下,一个NSTextStorage、NSLayoutManager、NSTextContainer为一一对应关系:

也可能有多个NSTextContainer:

或者是多个NSLayoutManager:

实际运用Demo1:高亮显示

效果:

  1. 新建HYHighlightTextStorage类,继承自NSTextStorage
  2. NSTextStorage的子类需要我们自己存储attributed string,在HYHighlightTextStorage中声明变量
NSMutableAttributedString *_mutableAttributedString;

并且重写以下4个抽象方法:

 - (NSString *)string;- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range;- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str;- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range;
  1. 修改text storage的内容时,需要3个步骤:
    (1) 首先调用beginEditing方法。
    (2) 通过调用 replaceCharactersInRange:withString: 或 setAttributes:range: 改变字符或属性
    (3)修改完成后调用endEditing,此时会调用代理方法 textStorage:willProcessEditing:range:changeInLength: 以及processEditing方法。

完整代码:
HYHighlightTextStorage.h

@interface HYHighlightTextStorage : NSTextStorage@end

HYHighlightTextStorage.m

#import "HYHighlightTextStorage.h"@implementation HYHighlightTextStorage{NSMutableAttributedString *_mutableAttributedString;NSRegularExpression *_expression;
}-(instancetype)init{if (self = [super init]) {_mutableAttributedString = [[NSMutableAttributedString alloc] init];_expression = [NSRegularExpression regularExpressionWithPattern:@"(\\*\\w+(\\s*\\w+)*\\s*\\*)" options:0 error:NULL];}return self;
}- (NSString *)string{return _mutableAttributedString.string;
}- (NSDictionary<NSString *,id> *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range {return [_mutableAttributedString attributesAtIndex:location effectiveRange:range];
}- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str {[self beginEditing];[_mutableAttributedString replaceCharactersInRange:range withString:str];[self edited:NSTextStorageEditedCharacters range:range changeInLength:(NSInteger)str.length - (NSInteger)range.length];[self endEditing];
}- (void)setAttributes:(NSDictionary<NSString *,id> *)attrs range:(NSRange)range {[self beginEditing];[_mutableAttributedString setAttributes:attrs range:range];[self edited:NSTextStorageEditedAttributes range:range changeInLength:0];[self endEditing];
}- (void)processEditing {[super processEditing];//去除当前段落的颜色属性NSRange paragaphRange = [self.string paragraphRangeForRange: self.editedRange];[self removeAttribute:NSForegroundColorAttributeName range:paragaphRange];//根据正则匹配,添加新属性[_expression enumerateMatchesInString:self.string options:NSMatchingReportProgress range:paragaphRange usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {[self addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:result.range];}];
}

HYHighlightViewController.m

#import "HYHighlightViewController.h"
#import "HYHighlightTextStorage.h"@interface HYHighlightViewController ()@property (nonatomic, strong) UITextView *textView;
@property (nonatomic, strong) HYHighlightTextStorage *textStorage;
@property (nonatomic, strong) NSTextContainer *textContainer;
@property (nonatomic, strong) NSLayoutManager *layoutManager;@end@implementation HYHighlightViewController- (void)viewDidLoad {[super viewDidLoad];_textContainer = [[NSTextContainer alloc] init];_layoutManager = [[NSLayoutManager alloc] init];_textStorage = [[HYHighlightTextStorage alloc] init];[_textStorage addLayoutManager:_layoutManager];[_layoutManager addTextContainer:_textContainer];_textView = [[UITextView alloc] initWithFrame:CGRectMake(10, 100, self.view.bounds.size.width-20, 300) textContainer:_textContainer];_textView.backgroundColor = [UIColor lightGrayColor];[self.view addSubview:_textView];[_textStorage replaceCharactersInRange:NSMakeRange(0, 0) withString:@"星号引起来的字符都会被*高亮*,*hello world* 星号引起来的字符都会"];
}

实际运用Demo2:文本元素与非文本元素混排

效果:

通过设置NSTextContainer的exclusionPaths属性,可以设置禁止填充文字的区域。此属性为NSArray数组,包含的是一组UIBezierPath数据,表示所有排除路径。如图:

完整代码:

#import "HYExclusionViewController.h"@interface HYExclusionViewController ()@property (nonatomic, strong) UITextView *textView;
@property (nonatomic, strong) NSTextStorage *textStorage;
@property (nonatomic, strong) NSTextContainer *textContainer;
@property (nonatomic, strong) NSLayoutManager *layoutManager;
@property (nonatomic, strong) UIView *exclusionView;@end@implementation HYExclusionViewController- (void)viewDidLoad {[super viewDidLoad];_textContainer = [[NSTextContainer alloc] init];_layoutManager = [[NSLayoutManager alloc] init];_textStorage   = [[NSTextStorage alloc] init];[_textStorage addLayoutManager:_layoutManager];[_layoutManager addTextContainer:_textContainer];_textView = [[UITextView alloc] initWithFrame:CGRectZero textContainer:_textContainer];_textView.frame = CGRectMake(10, 100, self.view.bounds.size.width-20, 300);_textView.backgroundColor = [UIColor lightGrayColor];[self.view addSubview:_textView];NSString *testString = @"a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a";[_textStorage replaceCharactersInRange:NSMakeRange(0, 0) withString:testString];[self setupExclusion];
}-(void)setupExclusion{//红圆圈_exclusionView = [[UIView alloc] initWithFrame:CGRectMake(140, 40, 120, 120)];_exclusionView.backgroundColor = [UIColor redColor];_exclusionView.layer.cornerRadius = 60;[self.textView addSubview:_exclusionView];CGRect originalPathRect = self.exclusionView.frame;CGFloat circle_X = originalPathRect.origin.x - self.textView.textContainerInset.left;CGFloat circle_Y = originalPathRect.origin.y - self.textView.textContainerInset.top;CGFloat circle_W = originalPathRect.size.width;CGFloat circle_H = originalPathRect.size.height;CGRect circleRect = CGRectMake(circle_X, circle_Y, circle_W, circle_H);UIBezierPath *exclusionCirclePath = [UIBezierPath bezierPathWithOvalInRect:circleRect];_textContainer.exclusionPaths = @[exclusionCirclePath];
}

实际运用Demo3:不规则的显示区域

效果:

NSTextContainer确定了文字的布局区域,默认是矩形,新建HYCustomTextContainer继承自NSTextContainer,重写NSTextContainer的-(CGRect)lineFragmentRectForProposedRect: atIndex: writingDirection: remainingRect:方法,返回每一行文字的位置个大小,即可将显示区域修改成需要的形状,例如圆形。

#import "HYCustomTextContainer.h"@implementation HYCustomTextContainer-(CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect atIndex:(NSUInteger)characterIndex writingDirection:(NSWritingDirection)baseWritingDirection remainingRect:(CGRect *)remainingRect{[super lineFragmentRectForProposedRect:proposedRectatIndex:characterIndexwritingDirection:baseWritingDirectionremainingRect:remainingRect];CGSize size = [self size];//圆半径CGFloat radius = fmin(size.width, size.height) * 0.5;CGFloat y = proposedRect.origin.y;CGFloat height = proposedRect.size.height;CGFloat width = 0;if (proposedRect.origin.y == 0) {width = 40.0;}else if(proposedRect.origin.y <= 2*radius){width = 2 * sqrt(powf(radius,2.0) - powf(fabs(y-radius), 2.0));}CGFloat x = radius - width/2.0;return CGRectMake(x, y, width, height);
}@end

实际运用Demo4:可点击字符的UILabel

效果:

  1. 新建HYLabel继承自UILabel
  2. 在初始化方法中初始化内部的NSTextStorage、NSLayoutManager、NSTextContainer。
  3. 重写drawTextInRect:方法,让我们自己的NSTextContainer去替代默认的展示控件。
  4. 重写touchesBegan:withEvent:方法,进行点击判断。

完整代码HYLabel.m代码:

#import "HYLabel.h"@interface HYLabel()@property (nonatomic, strong) NSTextStorage *textStorage;
@property (nonatomic, strong) NSLayoutManager *layoutManager;
@property (nonatomic, strong) NSTextContainer *textContainer;@end@implementation HYLabel-(void)setText:(NSString *)text{[super setText:text];[self setupTextSystem];[self.textStorage replaceCharactersInRange:NSMakeRange(0, 0) withString:text];
}-(void)setAttributedText:(NSAttributedString *)attributedText{[super setAttributedText:attributedText];[self setupTextSystem];[self.textStorage replaceCharactersInRange:NSMakeRange(0, 0) withAttributedString:attributedText];
}-(instancetype)initWithFrame:(CGRect)frame{if (self = [super initWithFrame:frame]) {self.userInteractionEnabled = YES;[self setupTextSystem];}return self;
}-(void)setupTextSystem{_textStorage = [[NSTextStorage alloc] init];_layoutManager = [[NSLayoutManager alloc] init];_textContainer = [[NSTextContainer alloc] init];[_textStorage addLayoutManager:_layoutManager];[_layoutManager addTextContainer:_textContainer];
}-(void)drawTextInRect:(CGRect)rect{NSRange range = NSMakeRange(0, self.textStorage.length);[self.layoutManager drawBackgroundForGlyphRange:range atPoint:CGPointMake(0, 0)];[self.layoutManager drawGlyphsForGlyphRange:range atPoint:CGPointMake(0, 0)];
}-(void)layoutSubviews{[super layoutSubviews];self.textContainer.size = self.bounds.size;
}-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{UITouch *touch = touches.anyObject;CGPoint point = [touch locationInView:self];//获取点击的字的indexNSUInteger glyphIndex = [self.layoutManager glyphIndexForPoint:point inTextContainer:self.textContainer];//获取字的rectCGRect glyphRect = [self.layoutManager boundingRectForGlyphRange:NSMakeRange(glyphIndex, 1) inTextContainer:self.textContainer];//最后判断点击位置是否在该字的显示范围内if(CGRectContainsPoint(glyphRect, point)){NSUInteger charaterIndex = [self.layoutManager characterIndexForGlyphAtIndex:glyphIndex];unichar charater = [[self.textStorage string] characterAtIndex:charaterIndex];if ([self.delegate respondsToSelector:@selector(didClickCharater:)]) {[self.delegate didClickCharater:charater];}}
}@end

实际运用Demo5:一个比较有趣的demo

网上看到的一个比较有趣的demo:https://www.jianshu.com/p/e72c441f14f3

参考:
TextKit 探究:https://www.jianshu.com/p/3f445d7f44d6
TextKit框架:https://www.jianshu.com/p/a12ecae89d6b
Using Text Kit to Draw and Manage Text:https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/CustomTextProcessing/CustomTextProcessing.html#//apple_ref/doc/uid/TP40009542-CH4-SW1
TextKit Best Practices:https://developer.apple.com/videos/play/wwdc2018/221/

TextKit及应用相关推荐

  1. TextKit简单示例

    TextKit简单示例 效果 源码 https://github.com/YouXianMing/Animations // // TextKitLoadImageController.m // An ...

  2. ios开发text kit_IOS开发入门之TextKit详解

    本文将带你了解IOS开发入门iOS 开发 富文本详解之TextKit详解,希望本文对大家学IOS有所帮助. textkit结构 textkit使用步骤 #Mark - 1. 自定义label  --c ...

  3. 图文混排(TextKit详解)

    iOS7 的发布给开发者的案头带来了很多新工具.其中一个就是 TextKit(文本工具箱).TextKit 由许多新的 UIKit 类组成,顾名思义,这些类就是用来处理文本的.在这里,我们将介绍 Te ...

  4. TextKit YYText

    TextKit http://www.jianshu.com/p/2f72a5fa99f1 这篇文章,对TextKit总结的很好,学习了下,没有深入-- 还有他提供的参考资料,值得细读: 提到的基本书 ...

  5. 用TextKit实现表情混排

    http://www.cocoachina.com/bbs/read.php?tid=144272 转载于:https://blog.51cto.com/5828666/1672507

  6. CoreText入坑一

    CoreText是Mac OS和iOS系统中处理文本的low-level API, 不管是使用OC还是swift, 实际我们使用CoreText都还是间接或直接使用C语言在写代码.CoreText是i ...

  7. 老司机 iOS 周报 #24 | 2018-06-25

    新闻 <WWDC 17 内参>免费订阅 去年我们组织针对 WWDC 17 的内容写了 25 篇文章,原来售价 39 元,现在免费开放给大家. "iPhone Only" ...

  8. iOS之富文本(二)

    之前做项目时遇到一个问题: 使用UITextView显示一段电影的简介,由于字数比较多,所以字体设置的很小,行间距和段间距也很小,一大段文字挤在一起看起来很别扭,想要把行间距调大,结果在XCode中查 ...

  9. IOS7 开发注意事项

    1,修改状态栏的样式和隐藏. 首先,需要在Info.plist配置文件中,增加键:UIViewControllerBasedStatusBarAppearance,并设置为YES: 然后,在UIVie ...

最新文章

  1. rtp 多媒体流同步控制 实时传输协议 简介
  2. 洛谷1042 乒乓球 解题报告
  3. opencv对应python版本_【求问各位大佬python3.6怎么使用opencv,用哪个版本】python3 opencv...
  4. 自定义对话框使用静态Handler传递参数
  5. Dump文件:线程dump和堆dump
  6. 计算机导论中IEE是什么缩写,Proc.IEE是期刊吗?全称是什么
  7. Java点击按钮div缩放_[Java教程]怎样给div增加resize事件
  8. 红帽 jboss_红帽正式宣布发布JBoss BPM Suite 6和JBoss BRMS 6
  9. Java字符串的十大问题
  10. [css] 说说浏览器解析CSS选择器的过程?
  11. mock 生成在线图片
  12. SD卡移植FAT32文件系统无MBR
  13. 数据结构拾遗(3) --红黑树的设计与实现(下)
  14. 中农大计算机组成原理在线作业1,河北农大2017计算机组成原理_在线作业_1课案.docx...
  15. html编辑器拖拽表格边框,Ueditor百度编辑器表格边框显示问题
  16. SpringMVC学习(三)RestFul风格
  17. python网络编程第三版网盘_Python网络编程(socketserver、TFTP云盘、HTTPServer服务器模型)...
  18. Unicode字符编码查询器。
  19. python走迷宫_python-走迷宫
  20. 给对象添加一个新对象

热门文章

  1. Python之向日志输出中添加上下文信息
  2. 创业笔记-Node.js入门之阻塞与非阻塞
  3. 《需求分析与系统设计》读书笔记1
  4. TCP/IP 协议理解
  5. (转)Unity3D - 性能优化之Draw Call
  6. win7,windowsXP安装mysql-5.1.49-win32,中文版、英文版,通吃
  7. 如何管理好自己的性格?
  8. ubuntu clion 创建桌面快捷方式
  9. memset函数详细说明
  10. jQuery 表格插件汇总