iOS富文本(二)初识Text Kit
概述
Text Kit
是建立在Core Text
上的文本布局系统,虽然没有Core Text
那么强大的文本处理功能,但是对于大多数常见的文本布局用Text Kit
能够很简单的实现,而不是用Core Text
底层的 API
去实现。在Text Kit
出现以前,UITextView
一直是基于WebKit
构建的,而iOS7
以及以后的版本UITextView
都基于Text Kit
来构建。下图是摘取苹果官方文档
展示了Text Kit
在iOS
文本与绘图框架中的位置,可见UILabel
,UITextField
,UITextView
都基于Text Kit
构建,而Text Kit
与WebKit
是基于Core Text
构建的。
本篇将介绍Text Kit
的一些基本结构与用法。
Text Kit的组件
- NSTextStorage
保存管理要显示的文本,NSTextStorage
是NSAttributeString
属性化字符串的子 类。所以NSTextStorage
不仅保存文本的内容还有文本的属性信息。 - NSLayoutManager
布局管理器,用来管理文本容器的布局 - NSTextContainer
表示文本显示的区域,这个区域通常是矩形的,但通过创建NSTextContainer的子类可以描述文本显示的区域为圆形,五边形或其他不规则的形状等。
每一个NSTextStorage对象有多个 NSLayoutManager对象,每个NSLayoutManager有多个NSTextContainer对象,所以对于指定的文本可以同时有多个布局,每种布局又可以有多个显示的区域。
下图摘自苹果官方文档
显示了Text Kit
中数据在每个组件中的流向
NSTextStorage对象存储的文本信息在NSTextContainer
定义的区域范围内由 Text views
对象来展示,由NSLayoutManager
对象控制布局。
Text Kit 组件使用
每一个UITextView
对象都有一个NSTextStorage
对象,对应一个NSLayoutManager
对象与一个NSLayoutManager
对象。每当UITextView
对象中的文字发生变化时,NSLayoutManager
都会监听到并且根据NSTextContainer
提供的绘制区域进行绘制,当布局完成,文本的当前显示状态被设为无效,然后文本管理器将排版好的文本设给文本视图。
下面通过一个简单的代码来描述UITextView
这三个组件是如何工作的。
通过Stroyboard
创建一个UITextView
对象与一个UIView
对象
创建UIView
对象的类,下面的TextKitView
类就是上图中青色的视图
@interface TextKitView ()
@property (weak,nonatomic) IBOutlet UITextView * textView;
@end
@implementation TextKitView
-(void)awakeFromNib
{
self.textView.layoutManager.delegate = self;
//设置NSLayoutManager的代理每当textView内容变化时都会重新布局并且在布局结束后出发代理方法
}
- (void)layoutManagerDidInvalidateLayout:(NSLayoutManager *)sender
{
[self setNeedsDisplay];
//每次textView内容变化时对TextKitView进行重绘
}
-(void)drawRect:(CGRect)rect
{
NSRange range = [self.textView.layoutManager glyphRangeForTextContainer:self.textView.textContainer];
//得到绘制的范围
[self.textView.layoutManager drawGlyphsForGlyphRange:range atPoint:CGPointMake(10, 10)];
//开始绘制
}
-(void)layoutSubviews
{
self.textContainer.size = self.frame.size;
// 设置绘制区域
}
@end
实现效果
每当textView
中的文字发生变化时青色视图中的文字也会同步改变
多容器布局
由于每个NSTextStorage
对象对应多个NSLayoutManager
对象,所以单独创建一个NSLayoutManager
对象来管理在上面代码中青色视图中的文字显示。
@interface TextKitView ()<NSLayoutManagerDelegate>
@property (weak,nonatomic) IBOutlet UITextView * textView;
@property (nonatomic,strong) NSTextContainer *textContainer;
@property (nonatomic,strong) NSLayoutManager *layoutManager;
@end
@implementation TextKitView
-(void)awakeFromNib
{
self.textContainer = [[NSTextContainer alloc] init];
self.layoutManager = [[NSLayoutManager alloc] init];
self.layoutManager.delegate = self;
[self.layoutManager addTextContainer:self.textContainer];
[self.textView.textStorage addLayoutManager:self.layoutManager];
}
- (void)layoutManagerDidInvalidateLayout:(NSLayoutManager *)sender
{
[self setNeedsDisplay];
}
-(void)drawRect:(CGRect)rect
{
NSLayoutManager *layoutManager = self.layoutManager;
NSRange range = [layoutManager glyphRangeForTextContainer:self.textContainer];;
[layoutManager drawGlyphsForGlyphRange:range atPoint:CGPointMake(10, 10)];
}
-(void)layoutSubviews
{
self.textContainer.size = self.frame.size;
}
@end
上面代码实现的效果与前一个例子相同,不同的是重新创建了一个NSLayoutManager
对象添加到textView
的NSTextStorage
对象中,新创建一个NSTextContainer
对象添加到NSLayoutManager
对象中。所以显示的内容是一样的但是拥有不同的容器布局。
代码示例在github 多容器布局 Tag下载
路径排除
Text Kit
可以实现在文本布局中排除一些区域让这些区域不显示文字,这对于一些图文混排的效果能够很轻松的实现。Text Kit
路径排除主要是用贝塞尔曲线来排除制定区域的文字,下面通过一段简单的代码来实现路径排除效果
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextView *textView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIBezierPath *path1 = [UIBezierPath bezierPath];
[path1 addArcWithCenter:CGPointMake(50 ,50)
radius:20
startAngle:0
endAngle:M_PI*2
clockwise:NO]; //(50,50)为圆心20为半径的圆
[path1 closePath];
UIBezierPath *path2 = [UIBezierPath bezierPath];
[path2 addArcWithCenter:CGPointMake(100 ,100)
radius:20
startAngle:0
endAngle:M_PI*2
clockwise:NO]; //(100,100)为圆心20为半径的圆
[path2 closePath];
self.textView.textContainer.exclusionPaths = @[path1,path2]; //设置要排除的路径
// Do any additional setup after loading the view, typically from a nib.
}
@end
实现效果
代码示例可以在github 路径排除 Tag下载