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绘图。