Text Kit学习日记–04–分页与绘制文本的策略
文章目录
准备
首先分页和绘制文本所用的NSAttributedString必须是一样的,包含相同的字体,段落格式,其中字体必须支持要绘制的文本语言,否则会在绘制时使用默认的字体代替
绘制文本其实就是在绘图,所以能够缓存更多纯文本外的信息,就可以提升效率,我们用来显示文本的容器有:
1.使用系统控件UITextView,UILabel,甚至是UIButton,需要配合TextKit使用
2.使用CATextLayer,但它有自己的段落格式,适合少量文本
3.使用UIView,重写drawRect方法,在上下文中直接绘制,通过setNeedsDisplay调用
4.使用CALayer,重写drawInContext,同UIView
5.使用UIImageView,把绘制结果保存成图片显示
分页
分页主要是取得每页的文本NSRange,文本长度较小时,使用TextKit
TextKit
(1)初始化NSTextStorage,NSLayoutManager,NSTextContainer
(2)计算总文本容器高度与单页文本容器高度
1- (CGRect)boundingRectForGlyphRange:(NSRange)glyphRange
2 inTextContainer:(NSTextContainer *)container;
两者相除结果加1即为总页数
(3)在一个while循环中创建文本容器并添加到layoutManager,然后调用
1- (NSRange)glyphRangeForTextContainer:(NSTextContainer *)container;
获取每页文本范围
(4)保存结果
追求效率的话,就必须使用CoreText
CoreText
(1)转换NSAttributedString为CFAttributedStringRef
(2)创建CTFrameSetterRef
(3)进入循环,首先添加路径,然后创建CTFrame,用文字填满Frame,然后获取页面内文字范围,然后保存结果
(4)释放CTFrameSetterRef
1CFAttributedStringRef cfAttStr = (__bridge CFAttributedStringRef)str;
2//直接桥接,引用计数不变
3CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(cfAttStr);
4int textPos = 0;
5_totalPage = 0;
6_currentPage = 0;
7NSUInteger strLength = [str length];
8while (textPos < strLength)
9{
10 CGPathRef path = CGPathCreateWithRect(textFrame, NULL);
11 //设置路径
12 CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(textPos, 0), path, NULL);
13 //生成frame
14 CFRange frameRange = CTFrameGetVisibleStringRange(frame);
15 NSRange ra = NSMakeRange(frameRange.location, frameRange.length);
16 [_pagingResult addObject:NSStringFromRange(ra)];
17 //获取范围并转换为NSRange,然后以NSString形式保存
18 textPos += frameRange.length;
19 //移动当前文本位置
20 CFRelease(frame);
21 CGPathRelease(path);
22 _totalPage++;
23 //释放路径和frame,页数加1
24}
25CFRelease(framesetter);
26//释放frameSetter
这里还要提一下NSString的计算文本矩形框的方法
NSStringDrawing
1- (CGRect)boundingRectWithSize:(CGSize)size
2 options:(NSStringDrawingOptions)options
3 context:(NSStringDrawingContext *)context;
它计算出的高度与使用CoreText计算出的高度是相同的,但应该仅限用于不分页的情况,因为网上的一些方法就是使用它先计算总高度,然后除以单页高度,然后结果加1,用这个结果对整个文本长度分段,当然,还有一些修正以适应页面,然后所有页面的文字长度都是相同的,的确这样效率非常高,但这个方法是错误的。为什么呢?对于文本中,某些片段内如果有大量的换行,此时换行是作为一个字符长度计算的,可想而知,绘制时有发生文字溢出页面的情况,所不推荐使用它来分页。
绘制
(1)最简单的方法
直接将NSAttributedString的子字符串赋值给控件的属性,比如UITextView的attributedString和CATextLayer的string,让控件自己绘制
(2)在上下文中绘制
1.使用CoreText在上下文中绘制,我们需要做的与上文中使用CoreText相同,只要增加一个在上下文中绘制的函数
1CTFrameDraw(frame, context);
2.使用NSStringDrawing,我们使用NSAttributedString,直接绘制即可,如果是NSString,需配置字典
1[str drawInRect:textFrame];
最后,如果是重写drawRect或者drawInContext,需要调用UIView或者CALayer的setNeedsDisplay在下一个绘图周期绘制,我采用的方法是直接赋值给CALayer的contents,这样会有动画效果,代码如下
1-(void)setNewStr:(NSAttributedString *)str
2{
3 UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
4 [st drawInRect:textFrame];
5 CGImageRef image = (CGBitmapContextCreateImage(UIGraphicsGetCurrentContext()));
6 UIGraphicsEndImageContext();
7 self.contents = (__bridge id)image;
8 CGImageRelease(image);
9}
这里需要注意的一点是,如果使用单一字体,比如默认中文字体来绘制中英文,CoreText的分页结果不会在使用NSStringDrawing时超出屏幕,但使用系统字体时,行高按较大的默认英文字体,绘制内容会超出屏幕,也就是混用字体时,最好用CoreText绘图。