CoreText图文混排 ,学习随笔记录

昨天研究coretext,在网上找了好多资料,然后总记录一下

CoreText 简介

CoreText 是用于处理文字和字体的底层技术。它直接和 Core Graphics(又被称为 Quartz)打交道。Quartz 是一个 2D 图形渲染引擎,能够处理 OSX 和 iOS 中的图形显示。

Quartz 能够直接处理字体(font)和字形(glyphs),将文字渲染到界面上,它是基础库中唯一能够处理字形的模块。因此,CoreText 为了排版,需要将显示的文本内容、位置、字体、字形直接传递给 Quartz。相比其它 UI 组件,由于 CoreText 直接和 Quartz 来交互,所以它具有高速的排版效果。

下图是 CoreText 的架构图,可以看到,CoreText 处于非常底层的位置,上层的 UI 控件(包括 UILabel,UITextField 以及 UITextView)和 UIWebView 都是基于 CoreText 来实现的。


UIWebView和CoreText区别分析

CoreText 的好处

  • CoreText 占用的内存更少,渲染速度快,UIWebView 占用的内存更多,渲染速度慢。
  • CoreText 在渲染界面前就可以精确地获得显示内容的高度(只要有了 CTFrame 即可),而 UIWebView 只有渲染出内容后,才能获得内容的高度(而且还需要用 javascript 代码来获取)
  • CoreText 的 CTFrame 可以在后台线程渲染,UIWebView 的内容只能在主线程(UI 线程)渲染。
  • 基于 CoreText 可以做更好的原生交互效果,交互效果可以更细腻。而 UIWebView 的交互效果都是用 javascript 来实现的,在交互效果上会有一些卡顿存在。例如,在 UIWebView 下,一个简单的按钮按下效果,都无法做到原生按钮的即时和细腻的按下效果。

CoreText 的劣势:

  • CoreText 渲染出来的内容不能像 UIWebView 那样方便地支持内容的复制。
  • 基于 CoreText 来排版需要自己处理很多复杂逻辑,例如需要自己处理图片与文字混排相关的逻辑,也需要自己实现链接点击操作的支持。
查看了资料,然后自己写一个图文混排和点击的效果,直接上代码
首先要引用CoreText类库

#import "CoreTextView.h"
#import "CoreText/CoreText.h"
#import "CoreText/CTRunDelegate.h"

@interface CoreTextView()

@property (assign ,nonatomic) CTFrameRef frame;

@end

@implementation CoreTextView


-(void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    
    // 步骤1:得到当前用于绘制画布的上下文,用于后续将内容绘制在画布上
    // 因为Core Text要配合Core Graphic 配合使用的,如Core Graphic一样,绘图的时候需要获得当前的上下文进行绘制
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 步骤2:翻转当前的坐标系(因为对于底层绘制引擎来说,屏幕左下角为(0,0))
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    
    // 步骤3:创建绘制区域
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.bounds);
    
    // 步骤4:创建需要绘制的文字与计算需要绘制的区域
    NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:@"通过CoreText进行学习绘制图文混排功能,通过CoreText进行学习绘制图文混排功能通过CoreText进行学习绘制图文混排功能通过CoreText进行学习绘制图文混排功能通过CoreText进行学习绘制图文混排功能"];

    
    // 步骤5:设置部分文字颜色
    [attrString addAttribute:(id)kCTForegroundColorAttributeName value:[UIColor greenColor] range:NSMakeRange(10, 10)];
    
    // 设置部分文字
    CGFloat fontSize = 20;
    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
    [attrString addAttribute:(id)kCTFontAttributeName value:(__bridge id)fontRef range:NSMakeRange(15, 10)];
    CFRelease(fontRef);
    
    // 设置行间距
    CGFloat lineSpacing = 10;
    const CFIndex kNumberOfSettings = 5;
    CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
        {kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing},
        {kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpacing},
        {kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpacing}
    };
    CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);
    [attrString addAttribute:(id)kCTParagraphStyleAttributeName value:(__bridge id)theParagraphRef range:NSMakeRange(0, attrString.length)];
    CFRelease(theParagraphRef);

    // 步骤6:图文混排部分
    // CTRunDelegateCallbacks:一个用于保存指针的结构体,由CTRun delegate进行回调
    CTRunDelegateCallbacks callbacks;
    memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));
    callbacks.version = kCTRunDelegateVersion1;
    callbacks.getAscent = ascentCallback;
    callbacks.getDescent = descentCallback;
    callbacks.getWidth = widthCallback;
    
    // 图片信息字典
    NSDictionary *imgInfoDic = @{@"width":@(self.bounds.size.width),@"height":@170};
    
    // 设置CTRun的代理
    CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)imgInfoDic);
    
    // 使用0xFFFC作为空白的占位符
    unichar objectReplacementChar = 0xFFFC;
    NSString *content = [NSString stringWithCharacters:&objectReplacementChar length:1];
    NSMutableAttributedString *space = [[NSMutableAttributedString alloc] initWithString:content];
    CFAttributedStringSetAttribute((CFMutableAttributedStringRef)space, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);
    CFRelease(delegate);
    
    // 将创建的空白AttributedString插入进当前的attrString中,位置可以随便指定,不能越界
    [attrString insertAttributedString:space atIndex:0];
    
    
    
    // 步骤7:根据AttributedString生成CTFramesetterRef
    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrString);
    _frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, [attrString length]), path, NULL);
    
    // 步骤8:进行绘制
    CTFrameDraw(_frame, context);
    
    // 步骤9:绘制图片
    UIImage *image = [UIImage imageNamed:@"image1.jpg"];
    CGContextDrawImage(context, [self calculateImagePositionInCTFrame:_frame], image.CGImage);
    
    // 步骤10.内存管理
//    CFRelease(_frame);//点击区域用到frame
    CFRelease(path);
    CFRelease(frameSetter);
}
#pragma mark - CTRun delegate 回调方法

static CGFloat ascentCallback(void *ref) {
    
    return [(NSNumber *)[(__bridge NSDictionary *)ref objectForKey:@"height"] floatValue];
}

static CGFloat descentCallback(void *ref) {
    
    return 0;
}

static CGFloat widthCallback(void *ref) {
    
    return [(NSNumber *)[(__bridge NSDictionary *)ref objectForKey:@"width"] floatValue];
}

/**
 *  根据CTFrameRef获得绘制图片的区域
 *
 *  @param ctFrame CTFrameRef对象
 *
 *  @return绘制图片的区域
 */
- (CGRect)calculateImagePositionInCTFrame:(CTFrameRef)ctFrame {
    
    // 获得CTLine数组
    NSArray *lines = (NSArray *)CTFrameGetLines(ctFrame);
    NSInteger lineCount = [lines count];
    CGPoint lineOrigins[lineCount];
    CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), lineOrigins);
    
    // 遍历每个CTLine
    for (NSInteger i = 0 ; i < lineCount; i++) {
        
        CTLineRef line = (__bridge CTLineRef)lines[i];
        NSArray *runObjArray = (NSArray *)CTLineGetGlyphRuns(line);
        
        // 遍历每个CTLine中的CTRun
        for (id runObj in runObjArray) {
            
            CTRunRef run = (__bridge CTRunRef)runObj;
            NSDictionary *runAttributes = (NSDictionary *)CTRunGetAttributes(run);
            CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(id)kCTRunDelegateAttributeName];
            if (delegate == nil) {
                continue;
            }
            
            NSDictionary *metaDic = CTRunDelegateGetRefCon(delegate);
            if (![metaDic isKindOfClass:[NSDictionary class]]) {
                continue;
            }
            
            CGRect runBounds;
            CGFloat ascent;
            CGFloat descent;
            
            runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
            runBounds.size.height = ascent + descent;
            
            CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
            runBounds.origin.x = lineOrigins[i].x + xOffset;
            runBounds.origin.y = lineOrigins[i].y;
            runBounds.origin.y -= descent;
            
            CGPathRef pathRef = CTFrameGetPath(ctFrame);
            CGRect colRect = CGPathGetBoundingBox(pathRef);
            
            CGRect delegateBounds = CGRectOffset(runBounds, colRect.origin.x, colRect.origin.y);
            return delegateBounds;
        }
    }
    return CGRectZero;
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //获取UITouch 对象
    UITouch* touch = [touches anyObject];
    //获取触摸点击当前view的坐标位置
    CGPoint location = [touch locationInView:self];
    
    //获取每一行
    CFArrayRef lines = CTFrameGetLines(_frame);
    CGPoint origins[CFArrayGetCount(lines)];
    
    //获取每行的原点坐标
    CTFrameGetLineOrigins(_frame, CFRangeMake(0, 0), origins);
    CTLineRef line = NULL;
    CGPoint lineOrigin = CGPointZero;
    for (int i = 0; i<CFArrayGetCount(lines); i++) {
        CGPoint origin = origins[i];
        CGPathRef path = CTFrameGetPath(_frame);
        //获取整个CTFrame的大小
        CGRect rect = CGPathGetBoundingBox(path);
        
        CGFloat y = rect.origin.y +rect.size.height-origin.y;
        
        //判断点击的位置处于那一行范围内
        if ((location.y <= y)&& (location.x >= origin.x)) {
            line = CFArrayGetValueAtIndex(lines, i);
            lineOrigin = origin;
            break;
        }
    }
    
     location.x -= lineOrigin.x;
    CFIndex index = CTLineGetStringIndexForPosition(line, location);
    
    if (index>=1&&index<=10) {
        NSLog(@"自己设计的点击区域");
    }
    
}



参考资料:http://www.saitjr.com/ios/use-coretext-make-typesetting-picture-and-text.html

                  http://blog.devtang.com/2015/06/27/using-coretext-1/

                  http://www.bkjia.com/IOSjc/536664.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值