本文技术点涉及到NSAttrbutedString
链接:http://blog.csdn.net/a316212802/article/details/50519733
简述
CoreText是用于处理文字和字体的底层技术,它直接和Core Graphics(又名Quartz2D)交流。Quartz是一个2D图形渲染引擎,能够处理OS X和iOS的图形显示问题
Quartz能够直接处理字体(font)和字形(glyphs)将文字渲染到界面上,它是基础库中的唯一能够处理字形的模块。因此,Core Text为了排版,需要将显示的文本内容 位置 字体字形直接传递给Quartz。与其他UI组件相比由于Core Text直接和Quartz交互 所以它具有搞笑的排版功能。
以下是Core Text架构图 (iOS7之后)
从这幅图可以看出 上层UI控件包括UILabel UITextField和UIWebView都是基于Core Text实现的。
CoreText和UIWebView的排版比较
CoreText和UIWebView都是处理复杂文字排版的备选方案 对于排版而言 CoreText和UIWebView的比较
优点:
- CoreText占用内存小,渲染速度更快 UIWebView占用内存多 渲染速度慢。
- CoreText在渲染界面前就可以精确获得显示内容高度 (有CTFrame就行)而UIWebView只有在渲染出内容才能获得内容的高度(通常需要和Javascript代码一同使用)
- CoreText的CTFrame可以在后台线程渲染,UIWebView的内容只能在主线程(UI线程)渲染。
- 基于CoreText可以做更好的原生交互效果 交互效果更细腻,而UIWebView得交互效果都是用js实现,在交互效果下会有卡顿情况的存在,例如 在UIWebView下一个简单的按钮点击事件 和CoreText就没办法比
CoreText渲染出来的内容不能像UIwebView那样方便复制
基于CoreText排版需要自己处理很多复杂的逻辑 例如需要自己处理图片与文字混排相关逻辑,也需要自己实现连接点击操作支持。
CoreText使用情景包括:新浪微博客户端,多看阅读客户端,猿题库。
Core Text内部结构如图
「
上图中大概显示了后半部分的结构,CTFrame 是指整个该UIView子控件的绘制区域,CTLine则是指每一行。CTRun则是每一段具有一样的属性的字符串 比如某段字体大小,颜色都一致的字符串为一个CTRun。CTRun不可以跨行,不管属性是否一致。通常的结构是每一个CTFrame有多个CTLine 每一个CTLine有多个CTRun。
一个简单的不包含图片链接等的文本显示demo的过程如下:
- 获取上下文
- 翻转坐标系
- 创建NSAttributedString
- 根据NSAttributedString创建CTFrameSetterRef对象
- 创建绘制区域CGPathRef
- 根据CTFramesetterRef和CGPathRef创建CTFrame
- CTFrameDraw绘制
- 手动释放创建的CTFramesetter CTFrame CGPathRef对象。
子控件的drawInRect代码如下:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
// 1.获取上下文
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// [a,b,c,d,tx,ty]
NSLog(@"转换前的坐标:%@",NSStringFromCGAffineTransform(CGContextGetCTM(contextRef)));
// 2.转换坐标系,CoreText的原点在左下角,UIKit原点在左上角
CGContextSetTextMatrix(contextRef, CGAffineTransformIdentity);
// 这两种转换坐标的方式效果一样
// 2.1
// CGContextTranslateCTM(contextRef, 0, self.bounds.size.height);
// CGContextScaleCTM(contextRef, 1.0, -1.0);
// 2.2
CGContextConcatCTM(contextRef, CGAffineTransformMake(1, 0, 0, -1, 0, self.bounds.size.height));
NSLog(@"转换后的坐标:%@",NSStringFromCGAffineTransform(CGContextGetCTM(contextRef)));
// 3.创建绘制区域,可以对path进行个性化裁剪以改变显示区域
CGMutablePathRef path = CGPathCreateMutable();
//CGPathAddRect(path, NULL, self.bounds);
CGPathAddEllipseInRect(path, NULL, self.bounds);
// 4.创建需要绘制的文字
NSMutableAttributedString *attributed = [[NSMutableAttributedString alloc] initWithString:@"关关雎鸠,在河之洲。窈窕淑女,君子好逑。 参差荇菜,左右流之。窈窕淑女,寤寐求之。求之不得,寤寐思服。悠哉悠哉,辗转反侧。参差荇菜,左右采之。窈窕淑女,琴瑟友之。参差荇菜,左右芼之。窈窕淑女,钟鼓乐之。"];
[attributed addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:18] range:NSMakeRange(0, 6)];
// 两种方式皆可
[attributed addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(3, 10)];
[attributed addAttribute:(id)kCTForegroundColorAttributeName value:[UIColor greenColor] range:NSMakeRange(0, 2)];
// 设置行距等样式
CGFloat lineSpace = 10; // 行距一般取决于这个值
CGFloat lineSpaceMax = 20;
CGFloat lineSpaceMin = 2;
const CFIndex kNumberOfSettings = 3;
// 结构体数组
CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
{kCTParagraphStyleSpecifierLineSpacingAdjustment,sizeof(CGFloat),&lineSpace},
{kCTParagraphStyleSpecifierMaximumLineSpacing,sizeof(CGFloat),&lineSpaceMax},
{kCTParagraphStyleSpecifierMinimumLineSpacing,sizeof(CGFloat),&lineSpaceMin}
};
CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);
// 单个元素的形式
// CTParagraphStyleSetting theSettings = {kCTParagraphStyleSpecifierLineSpacingAdjustment,sizeof(CGFloat),&lineSpace};
// CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(&theSettings, kNumberOfSettings);
// 两种方式皆可
// [attributed addAttribute:(id)kCTParagraphStyleAttributeName value:(__bridge id)theParagraphRef range:NSMakeRange(0, attributed.length)];
// 将设置的行距应用于整段文字
[attributed addAttribute:NSParagraphStyleAttributeName value:(__bridge id)(theParagraphRef) range:NSMakeRange(0, attributed.length)];
CFRelease(theParagraphRef);
// 5.根据NSAttributedString生成CTFramesetterRef
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributed);
CTFrameRef ctFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributed.length), path, NULL);
// 6.绘制除图片以外的部分
CTFrameDraw(ctFrame, contextRef);
// 7.内存管理,ARC不能管理CF开头的对象,需要我们自己手动释放内存
CFRelease(path);
CFRelease(framesetter);
CFRelease(ctFrame);
}
执行效果如下:
代码注意点:
- DrawInRect方法不是本文重点 不解释
- 为什么翻转?坐标系原点是在左下角,UIKit的坐标原点在左上角。
- 创建绘制区域
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
当前是用Quartz创建了矩形加入到区域路径中,如果替换为
CGPathAddEllipseInRect(path,NULL,self.bounds),则会发现可绘制区域变成一个椭圆。
下一篇将会涉及到图文混排的内容。