一、引言
在iOS开发中,处理文本的视图控件主要有4中,UILabel,UITextField,UITextView和UIWebView。其中UILabel与UITextField相对简单,UITextView是功能完备的文本布局展示类,通过它可以进行复杂的富文本布局,UIWebView主要用来加载网页或者pdf文件,其可以进行HTML,CSS和JS等文件的解析。
TextKit是一个偏上层的开发框架,在iOS7以上可用,使用它开发者可以方便灵活处理复杂的文本布局,满足开发中对文本布局的各种复杂需求。TextKit实际上是基于CoreText的一个上层框架,其是面向对象的,如果TextKit中提供的API无法满足需求,可以使用CoreText中的API进行更底层的开发。
官方文档中的一张图片很确切,经常会被用来描述TextKit框架在iOS系统文本渲染中所处的位置。
二、TextKit框架的结构
界面在进行文本的渲染时,有下面几个必要条件:
1.要渲染展示的内容。
2.将内容渲染在某个视图上。
3.内容渲染在视图上的尺寸位置和形状。
在TextKit框架中,提供了几个类分别对应处理上述的必要条件:
1.NSTextStorage对应要渲染展示的内容。
2.UITextView对应要渲染的视图。
3.NSTextContainer对应渲染的尺寸位置和形状信息。
除了上述3个类之外,TextKit框架中的NSLayoutManager类作为协调者来进行布局操作。
上述关系如下图所示:
三、使用TextKit进行文本布局流程
个人理解,TextKit主要用于更精细的处理文本布局以及进行复杂的图文混排布局,使用TextKit进行文本的布局展示十分繁琐,首先需要将显示内容定义为一个NSTextStorage对象,之后为其添加一个布局管理器对象NSLayoutManager,在NSLayoutManager中,需要进行NSTextContainer的定义,定义多了NSTextContainer对象则会将文本进行分页。最后,将要展示的NSTextContainer绑定到具体的UITextView视图上。
示例代码如下:
//定义Container
NSTextContainer * container = [[NSTextContainer alloc]initWithSize:CGSizeMake(150, 200)];
//定义布局管理类
NSLayoutManager * layoutManager = [[NSLayoutManager alloc]init];
//将container添加进布局管理类管理
[layoutManager addTextContainer:container];
//定义一个Storage
NSTextStorage * storage = [[NSTextStorage alloc]initWithString:@"The NSTextContainer class defines a region where text is laid out. An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on."];
//为Storage添加一个布局管理器
[storage addLayoutManager:layoutManager];
//将要显示的container与视图TextView绑定
UITextView * textView = [[UITextView alloc]initWithFrame:self.view.frame textContainer:container];
[self.view addSubview:textView];
上面代码演示的过程如下图所示:
需要注意,TextKit进行布局的核心思路是最终的视图对应一个文本块Container,并不是一段文本内容Storage,LayoutManager会将完整的内容根据其中Container的尺寸进行分页,TextView根据需要显示的部分进行Container的选择。
四、了解NSTextContainer类
NSTextContainer可以简单理解为创建一个文本区块,文本内容将在这个区块中进行渲染,其中常用属性与方法如下:
//初始化方法 设置区块的尺寸
- (instancetype)initWithSize:(CGSize)size;
//与其绑定的layoutManager 需要注意,不是设置这个属性 使用[NSLayoutManager addTextContainer:]方式来进行绑定
@property(nullable, assign, NS_NONATOMIC_IOSONLY) NSLayoutManager *layoutManager;
//替换绑定的布局管理类对象
- (void)replaceLayoutManager:(NSLayoutManager *)newLayoutManager;
//获取区块尺寸
@property(NS_NONATOMIC_IOSONLY) CGSize size;
//设置从区块中剔除某一区域
@property(copy, NS_NONATOMIC_IOSONLY) NSArray<UIBezierPath *> *exclusionPaths;
//设置截断模式 需要注意 这个属性的设置只是会影响此区块的最后一行的截断模式
@property(NS_NONATOMIC_IOSONLY) NSLineBreakMode lineBreakMode;
//设置每行文本左右空出的间距
@property(NS_NONATOMIC_IOSONLY) CGFloat lineFragmentPadding;
//设置TextView上可输入的文本最大行数
@property(NS_NONATOMIC_IOSONLY) NSUInteger maximumNumberOfLines;
//这个方法用于提供给子类进行重写 这里返回的Rect是可以布局文本的区域
- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect atIndex:(NSUInteger)characterIndex writingDirection:(NSWritingDirection)baseWritingDirection remainingRect:(nullable CGRect *)remainingRect;
//这个BOOL值的属性决定Container的宽度是否自适应TextView的宽度
@property(NS_NONATOMIC_IOSONLY) BOOL widthTracksTextView;
//这个BOOL值的属性决定Container的高度是否自适应TextView的高度
@property(NS_NONATOMIC_IOSONLY) BOOL heightTracksTextView;
上面所列举的方法中,exclusionPaths属性十分强大,通过设置它,可以将布局区域内剔出一块区域不进行布局,示例代码如下:
[super viewDidLoad];
NSTextContainer * container = [[NSTextContainer alloc]initWithSize:CGSizeMake(300, 500)];
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:70 startAngle:0 endAngle:M_PI*2 clockwise:YES];
container.exclusionPaths = @[path];
container.lineBreakMode = NSLineBreakByCharWrapping;
NSLayoutManager * layoutManager = [[NSLayoutManager alloc]init];
[layoutManager addTextContainer:container];
NSTextStorage * storage = [[NSTextStorage alloc]initWithString:@"The NSTextContainer class defines a region where text is laid out. An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on. An NSTextContainer object normally defines rectangular regions, but you can define exclusion paths inside the text container to create regions where text does not flow. You can also subclass to create text containers with nonrectangular regions, such as circular regions, regions with holes in them, or regions that flow alongside graphics.The NSTextContainer class defines a region where text is laid out. An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on. An NSTextContainer object normally defines rectangular regions, but you can define exclusion paths inside the text container to create regions where text does not flow. You can also subclass to create text containers with nonrectangular regions, such as circular regions, regions with holes in them, or regions that flow alongside graphics.An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on. An NSTextContainer object normally defines rectangular regions, but you can define exclusion paths inside the text container to create regions where text does not flow. You can also subclass to create text containers with nonrectangular regions, such as circular regions, regions with holes in them, or regions that flow alongside graphics."];
[storage addLayoutManager:layoutManager];
UITextView * textView = [[UITextView alloc]initWithFrame:self.view.frame textContainer:container];
[self.view addSubview:textView];
效果如下图: