前文:CoreText图文混排的主要的实现原理就是在富文本中插入一个空白的图片占位符的富文本字符串,通过代理设置相关的图片尺寸信息,根据从富文本得到的frame计算图片绘制的frame再绘制图片这么一个过程。

.h文件

#import <UIKit/UIKit.h>

@interface HSCoreTextView : UIView

@end

.m文件

#import "HSCoreTextView.h"
#import <CoreText/CoreText.h>

@implementation HSCoreTextView


//图片回调函数
static CGFloat ascentCallBacks(void *ref)
{
    //__bridge既是C的结构体转换成OC对象时需要的一个修饰词
    return [[(__bridge NSDictionary *)ref valueForKey:@"height"] floatValue];
}

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

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

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];
    
    //1.绘制上下文
    //1.1获取绘制上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    //1.2.coreText 起初是为OSX设计的,而OSX得坐标原点是左下角,y轴正方向朝上。iOS中坐标原点是左上角,y轴正方向向下。若不进行坐标转换,则文字从下开始,还是倒着的,因此需要设置以下属性
    设置字形的变换矩阵为不做图形变换
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    //平移方法,将画布向上平移一个屏幕高
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    //缩放方法,x轴缩放系数为1,则不变,y轴缩放系数为-1,则相当于以x轴为轴旋转180度
    CGContextScaleCTM(context, 1.0, -1.0);
    
    //2.设置图片回调函数
    //2.1创建一个回调结构体,设置相关参数
    CTRunDelegateCallbacks callBacks;
    //memset将已开辟内存空间 callbacks 的首 n 个字节的值设为值 0, 相当于对CTRunDelegateCallbacks内存空间初始化
    memset(&callBacks, 0, sizeof(CTRunDelegateCallbacks));
    //2.2设置回调版本,默认这个
    callBacks.version = kCTRunDelegateVersion1;
    //2.3设置图片顶部距离基线的距离
    callBacks.getAscent = ascentCallBacks;
    //2.4设置图片底部距离基线的距离
    callBacks.getDescent = descentCallBacks;
    //2.5设置图片宽度
    callBacks.getWidth = widthCallBacks;
    //2.6创建一个代理
    NSDictionary *dicPic = @{@"height":@"60",@"width":@"60"};
    CTRunDelegateRef delegate = CTRunDelegateCreate(&callBacks, (__bridge void * _Nullable)(dicPic));

    //3.插入图片
    //创建空白字符
    unichar placeHolder = 0xFFFC;
    NSString *placeHolderStr = [NSString stringWithCharacters:&placeHolder length:1];
    NSMutableAttributedString *placeHolderMabString = [[NSMutableAttributedString alloc] initWithString:placeHolderStr];
    //给字符串中的范围中字符串设置代理
    CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderMabString, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);
    
    //4.设置要显示的文字
    NSMutableAttributedString *mabString = [[NSMutableAttributedString alloc] initWithString:@"\n这里在测试图文混排,\n我是富文本"];
    [mabString insertAttributedString:placeHolderMabString atIndex:12];
    
    //5.绘制文本
    //5.1.创建CTFramesetterRef
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)mabString);
    
    //5.2.创建路径
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.bounds);
    
    //5.3.创建CTFrame
    NSInteger length = mabString.length;
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, length), path, NULL);
    CTFrameDraw(frame, context);
    
    //6.添加图片并绘制
    UIImage *image = [UIImage imageNamed:@"icon-60"];
    CGRect imgFrm = [self calculateImageRectWithFrame:frame];
    CGContextDrawImage(context, imgFrm, image.CGImage);
    
    //7.释放
    CFRelease(delegate);
    CFRelease(frame);
    CFRelease(path);
    CFRelease(framesetter);
}

#pragma mark 计算图片Frame
- (CGRect)calculateImageRectWithFrame:(CTFrameRef)frame
{
    //根据frame获取需要绘制的线的数组
    NSArray *arrLines = (NSArray *)CTFrameGetLines(frame);
    NSInteger count = arrLines.count;
    CGPoint points[count];
    //获取起始点位置
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), points);
    
    for (int i = 0; i < count; i ++) {
        CTLineRef line = (__bridge CTLineRef)(arrLines[i]);
        //CTRun 或者叫做 Glyph Run,是一组共享想相同attributes(属性)的字形的集合体
        NSArray *arrGlyphRun = (NSArray *)CTLineGetGlyphRuns(line);
        for (int j = 0; j < arrGlyphRun.count; j ++) {
            CTRunRef run = (__bridge CTRunRef)(arrGlyphRun[j]);
            //获取CTRun的属性
            NSDictionary *attributes = (NSDictionary *)CTRunGetAttributes(run);
            CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[attributes valueForKey:(id)kCTRunDelegateAttributeName];
            if (delegate == nil) {
                continue;
            }
            
            NSDictionary *dic = CTRunDelegateGetRefCon(delegate);
            if (![dic isKindOfClass:[NSDictionary class]]) {
                continue;
            }
            
            //获取一个起点
            CGPoint point = points[i];
            //获取上下距
            CGFloat ascent,desecent;
            //创建一个Frame
            CGRect boundsRun;
            boundsRun.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &desecent, NULL);
            boundsRun.size.height = ascent + desecent;
            //获取偏移量
            CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
            boundsRun.origin.x = point.x + xOffset;
            boundsRun.origin.y = point.y - desecent;
            //获取绘制路径
            CGPathRef path = CTFrameGetPath(frame);
            //获取剪裁区域边框
            CGRect colRect = CGPathGetBoundingBox(path);
            CGRect imageBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
            
            return imageBounds;
        }
    }
    return CGRectZero;
}

@end


参考链接:

http://www.jianshu.com/p/6db3289fb05d