【iOS开发】CoreText的使用(1)

本文技术点涉及到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之后)

【iOS开发】CoreText的使用(1)_第1张图片

从这幅图可以看出 上层UI控件包括UILabel UITextField和UIWebView都是基于Core Text实现的。


CoreText和UIWebView的排版比较

CoreText和UIWebView都是处理复杂文字排版的备选方案 对于排版而言 CoreText和UIWebView的比较

优点:

  1. CoreText占用内存小,渲染速度更快 UIWebView占用内存多 渲染速度慢。
  2. CoreText在渲染界面前就可以精确获得显示内容高度 (有CTFrame就行)而UIWebView只有在渲染出内容才能获得内容的高度(通常需要和Javascript代码一同使用)
  3. CoreText的CTFrame可以在后台线程渲染,UIWebView的内容只能在主线程(UI线程)渲染。
  4. 基于CoreText可以做更好的原生交互效果 交互效果更细腻,而UIWebView得交互效果都是用js实现,在交互效果下会有卡顿情况的存在,例如 在UIWebView下一个简单的按钮点击事件 和CoreText就没办法比
劣势:
CoreText渲染出来的内容不能像UIwebView那样方便复制
基于CoreText排版需要自己处理很多复杂的逻辑 例如需要自己处理图片与文字混排相关逻辑,也需要自己实现连接点击操作支持。

CoreText使用情景包括:新浪微博客户端,多看阅读客户端,猿题库。

Core Text内部结构如图

【iOS开发】CoreText的使用(1)_第2张图片


上图中大概显示了后半部分的结构,CTFrame 是指整个该UIView子控件的绘制区域,CTLine则是指每一行。CTRun则是每一段具有一样的属性的字符串 比如某段字体大小,颜色都一致的字符串为一个CTRun。CTRun不可以跨行,不管属性是否一致。通常的结构是每一个CTFrame有多个CTLine 每一个CTLine有多个CTRun。

一个简单的不包含图片链接等的文本显示demo的过程如下

  1. 获取上下文
  2. 翻转坐标系
  3. 创建NSAttributedString
  4. 根据NSAttributedString创建CTFrameSetterRef对象
  5. 创建绘制区域CGPathRef
  6. 根据CTFramesetterRef和CGPathRef创建CTFrame
  7. CTFrameDraw绘制
  8. 手动释放创建的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);
}
执行效果如下:

【iOS开发】CoreText的使用(1)_第3张图片

代码注意点:

  1. DrawInRect方法不是本文重点 不解释
  2. 为什么翻转?坐标系原点是在左下角,UIKit的坐标原点在左上角。
  3. 创建绘制区域 

    CGMutablePathRef path = CGPathCreateMutable();

    CGPathAddRect(path, NULL, self.bounds);

    当前是用Quartz创建了矩形加入到区域路径中,如果替换为

    CGPathAddEllipseInRect(path,NULL,self.bounds),则会发现可绘制区域变成一个椭圆。

     下一篇将会涉及到图文混排的内容。


你可能感兴趣的:(【iOS开发】CoreText的使用(1))