CoreText实现图文混排

作者: 老司机Wicky | 来源:发表于2016-05-15 22:42 被阅读22605次
    CoreText实现图文混排

    系列文章:


    也好久没来写博客了,主要是最近也工作了,手头的事有点多,一时间也就断了,闲下来了我就来补博客了,刚好最近也做了很多东西,放在这里也算给自己做个笔记吧。


    2016.12.25补充:最新demo在第三篇文章末尾。


    CoreText

    最近公司做了一个项目,需要用到图文混排技术。于是呢就疯狂地在网上搜刮资料。
    不过很不幸的是,百度的CoreText资料还是比较少滴,翻来覆去就那几个版本。
    然而我又上不去谷歌,so,困难重重啊。
    不过虽然资料少,不够前辈们给的贡献终于还是在我的努力下都被我消化了,然后我也来做个笔记。


    CoreText的介绍

    Core Text 是基于 iOS 3.2+ 和 OSX 10.5+ 的一种能够对文本格式和文本布局进行精细控制的文本引擎。
    它良好的结合了 UIKit 和 Core Graphics/Quartz:

    UIKit 的 UILabel 允许你通过在 IB 中简单的拖曳添加文本,但你不能改变文本的颜色和其中的单词。  
    Core Graphics/Quartz几乎允许你做任何系统允许的事情,但你需要为每个字形计算位置,并画在屏幕上。
    Core Text 正结合了这两者!你可以完全控制位置、布局、类似文本大小和颜色这样的属性,而 Core Text 将帮你完善其它的东西——类似文本换行、字体呈现等等。
    

    以上就是对CoreText的介绍。


    老司机对CoreText实现图文混排的一些理解

    老司机认为,图文混排中使用到的CoreText只是CoreText庞大体系中一个对富文本的增强的一部分。
    我个人想法啊,我读书少,理解的可能不到位,不过你咬我啊。

    恩,我又逗逼了一波,说好的大师气质呢,下面开始严肃了啊。

    严肃的就是iOS7新推出的类库Textkit,其实是在之前推出的CoreText上的封装,根据苹果的说法,他们开发了两年多才完成,而且他们在开发时候也将表情混排作为一个使用案例进行研究,所以要实现表情混排将会非常容易

    苹果引入TextKit的目的并非要取代已有的CoreText框架,虽然CoreText的主要作用也是用于文字的排版和渲染,但它是一种先进而又处于底层技术,如果我们需要将文本内容直接渲染到图形上下文(Graphics context)时,从性能和易用性来考虑最佳方案就是使用CoreText
    原理的东西学一学总没有坏处。因此,还是有必要去学一学CoreText的。
    那我们开始学习吧。


    富文本

    老司机说过,我要讲的只是用来增强富文本的那一部分,那么富文本怎么使用呢。

    富文本是什么呢?

    富文本格式(RTF)规范是为了便于在应用程序之间轻松转储格式化文本和图形的一种编码方法。
    现在,用户可以利用特定转换软件,在不同系统如MS-DOS、Windows、OS/2、Macintosh和Power Macintosh的应用程序之间转移字处理文档。
    RTF规范提供一种在不同的输出设备、操作环境和操作系统之间交换文本和图形的一种格式。
    RTF使用ANSI, PC-8, Macintosh, 或IBM PC字符集控制文档的表示法和格式化,包括屏幕显示和打印
    。凭借RTF规范,不同的操作系统和不同的软件程序创建的文档能够在这些操作系统和应用程序之间传递。
    将一个格式化的文件转换为RTF文件的软件称为RTF书写器。
    RTF书写器用于分离现有文本中的程序控制信息,并且生成一个包含文本和与之相关的RTF组的新文件。
    将RTF文件转换成格式化文件的软件则称为RTF阅读器。
    

    简单的说,附带有每一个文字属性的字符串,就是富文本。
    在iOS中,我们有一个专门的类来处理富文本 AttributeString


    富文本的基本使用方法

    诶,标题越来越小了,都4个#号了,说明我扯远了啊。不过要想使用CoreText不会富文本还是不行啊。

    来吧。
    AttributedString也分为NSAttributedStringNSMutableAttributedString两个类,类似于String,我就不赘述了。
    富文本本质上没有什么难度,只要给指定的字符串附上指定的属性就好了。下面给出富文本的一些基本方法。

    • -initWithString:以NSString初始化一个富文本对象
    • -setAttributes:range:为富文本中的一段范围添加一些属性,第一个参数是个NSDictionary字典,第二个参数是NSRange。
    • -addAttribute:value:range:添加一个属性
    • -addAttributes:range:添加多个属性
    • -removeAttribute:range:移除属性
      额,老司机知道这么说不直观,来来来,上代码。
    NSDictionary * dic = @{NSFontAttributeName:[UIFont fontWithName:@"Zapfino" size:20],NSForegroundColorAttributeName:[UIColor redColor],NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle)};
        NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"0我是一个富文本,9听说我有很多属性,19I will try。32这里清除属性."];
    //    设置属性
        [attributeStr setAttributes:dic range:NSMakeRange(0, attributeStr.length)];
    //    添加属性
        [attributeStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:30] range:NSMakeRange(9, 10)];
        [attributeStr addAttribute:NSForegroundColorAttributeName value:[UIColor cyanColor] range:NSMakeRange(13, 13)];
    //    添加多个属性
        NSDictionary * dicAdd = @{NSBackgroundColorAttributeName:[UIColor yellowColor],NSLigatureAttributeName:@1};
        [attributeStr addAttributes:dicAdd range:NSMakeRange(19, 13)];
    //    移除属性
        [attributeStr removeAttribute:NSFontAttributeName range:NSMakeRange(32, 9)];
        UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 400)];
        label.numberOfLines = 0;
        label.attributedText = attributeStr;
    

    这里你要注意一下,给label的一定是给他的attributedText属性,你给text是不行的。
    是不是用起来很简单,富文本,跟字典没什么区别么。


    CoreText绘制富文本

    是不是终于进入正题了。其实之所以说那么多,还是为了你看完就能保证会用啊,否则你不会富文本你自己还要查找富文本相关资料。

    Come On!

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

    先来整体代码

    -(void)drawRect:(CGRect)rect
    {
        [super drawRect:rect];
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetTextMatrix(context, CGAffineTransformIdentity);    CGContextTranslateCTM(context, 0, self.bounds.size.height);
        CGContextScaleCTM(context, 1.0, -1.0);
        NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"\n这里在测试图文混排,\n我是一个富文本"];
        CTRunDelegateCallbacks callBacks;
        memset(&callBacks,0,sizeof(CTRunDelegateCallbacks));
        callBacks.version = kCTRunDelegateVersion1;
        callBacks.getAscent = ascentCallBacks;
        callBacks.getDescent = descentCallBacks;
        callBacks.getWidth = widthCallBacks;
        NSDictionary * dicPic = @{@"height":@129,@"width":@400};
        CTRunDelegateRef delegate = CTRunDelegateCreate(& callBacks, (__bridge void *)dicPic);
        unichar placeHolder = 0xFFFC;
        NSString * placeHolderStr = [NSString stringWithCharacters:&placeHolder length:1];
        NSMutableAttributedString * placeHolderAttrStr = [[NSMutableAttributedString alloc] initWithString:placeHolderStr];
        CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderAttrStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);
        CFRelease(delegate);
        [attributeStr insertAttributedString:placeHolderAttrStr atIndex:12];
        CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathAddRect(path, NULL, self.bounds);
        NSInteger length = attributeStr.length;
        CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, length), path, NULL);
        CTFrameDraw(frame, context);
        
        UIImage * image = [UIImage imageNamed:@"bd_logo1"];
        CGRect imgFrm = [self calculateImageRectWithFrame:frame];
        CGContextDrawImage(context,imgFrm, image.CGImage);
        CFRelease(frame);
        CFRelease(path);
        CFRelease(frameSetter);
    }
    static CGFloat ascentCallBacks(void * ref)
    {
        return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"height"] floatValue];
    }
    static CGFloat descentCallBacks(void * ref)
    {
        return 0;
    }
    static CGFloat widthCallBacks(void * ref)
    {
        return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"width"] floatValue];
    }
    
    
    -(CGRect)calculateImageRectWithFrame:(CTFrameRef)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];
            NSArray * arrGlyphRun = (NSArray *)CTLineGetGlyphRuns(line);
            for (int j = 0; j < arrGlyphRun.count; j ++) {
                CTRunRef run = (__bridge CTRunRef)arrGlyphRun[j];
                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;
                CGFloat descent;
                CGRect boundsRun;
                boundsRun.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
                boundsRun.size.height = ascent + descent;
                CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
                boundsRun.origin.x = point.x + xOffset;
                boundsRun.origin.y = point.y - descent;
                CGPathRef path = CTFrameGetPath(frame);
                CGRect colRect = CGPathGetBoundingBox(path);
                CGRect imageBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
                return imageBounds;
            }
        }
        return CGRectZero;
    }
    

    不瞒你说,我看着代码都烦,也怕,所以放心,老司机会一句一句给你解释的。


    分段解析

    准备工作

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);    
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    

    先要来一个背景介绍哈

    /*
     coreText 起初是为OSX设计的,而OSX得坐标原点是左下角,y轴正方向朝上。iOS中坐标原点是左上角,y轴正方向向下。
     若不进行坐标转换,则文字从下开始,还是倒着的
        如下图(盗的图,别打我)
     */
    
    系统坐标系
    这四句什么意思呢?
    首先第一句。
    CGContextRef context = UIGraphicsGetCurrentContext();//获取当前绘制上下文
    

    为什么要回去上下文呢?因为我们所有的绘制操作都是在上下文上进行绘制的。

    然后剩下的三句。
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);//设置字形的变换矩阵为不做图形变换  
        CGContextTranslateCTM(context, 0, self.bounds.size.height);//平移方法,将画布向上平移一个屏幕高  
        CGContextScaleCTM(context, 1.0, -1.0);//缩放方法,x轴缩放系数为1,则不变,y轴缩放系数为-1,则相当于以x轴为轴旋转180度
    

    正如之上的背景说的,coreText使用的是系统坐标,然而我们平时所接触的iOS的都是屏幕坐标,所以要将屏幕坐标系转换系统坐标系,这样才能与我们想想的坐标互相对应。
    事实上呢,这三句是翻转画布的固定写法,这三句你以后会经常看到的。

    继续。


    图片的代理的设置

    /*
      事实上,图文混排就是在要插入图片的位置插入一个富文本类型的占位符。通过CTRUNDelegate设置图片
    */
    
    NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"\n这里在测试图文混排,\n我是一个富文本"];//这句不用我多说吧,最起码得有个富文本啊才能插入不是。
    
    /*
     设置一个回调结构体,告诉代理该回调那些方法
     */
    CTRunDelegateCallbacks callBacks;//创建一个回调结构体,设置相关参数
    memset(&callBacks,0,sizeof(CTRunDelegateCallbacks));//memset将已开辟内存空间 callbacks 的首 n 个字节的值设为值 0, 相当于对CTRunDelegateCallbacks内存空间初始化
    callBacks.version = kCTRunDelegateVersion1;//设置回调版本,默认这个
    callBacks.getAscent = ascentCallBacks;//设置图片顶部距离基线的距离
    callBacks.getDescent = descentCallBacks;//设置图片底部距离基线的距离
    callBacks.getWidth = widthCallBacks;//设置图片宽度
    

    注意了,这里经GreyLove提醒有重要改动,就是这里,添加了memset,码字的时候少忘码了一句话。怪我粗心,十分抱歉,我会通知每一个留言的同学。

    为什么要设置一个回调结构体呢?
    因为coreText中大量的调用c的方法。事实上你会发现大部分跟系统底层有关的都需要调c的方法。所以设置代理要按照人家的方法来啊。

    看看这几句代码也很好懂,就是注释中写的意思。
    后三句分别就是说当我需要走这些代理的时候都会走那些代理方法
    好吧,扯到这又要补充知识了。这个距离什么东西呢?

    字形

    对对,这呢就是一个CTRun的尺寸图,什么你问CTRun是啥?还没到那呢,后面会详细介绍。
    在这你只要知道,一会我们绘制图片的时候实际上实在一个CTRun中绘制这个图片,那么CTRun绘制的坐标系中,他会以origin点作为原点进行绘制。
    基线为过原点的x轴ascent即为CTRun顶线距基线的距离descent即为底线距基线的距离
    我们绘制图片应该从原点开始绘制,图片的高度及宽度及CTRun的高度及宽度,我们通过代理设置CTRun的尺寸间接设置图片的尺寸。


    /*
     创建一个代理
    */
        NSDictionary * dicPic = @{@"height":@129,@"width":@400};//创建一个图片尺寸的字典,初始化代理对象需要
        CTRunDelegateRef delegate = CTRunDelegateCreate(& callBacks, (__bridge void *)dicPic);//创建代理
    

    上面只是设置了回调结构体,然而我们还没有告诉这个代理我们要的图片尺寸
    所以这句话就在设置代理的时候绑定了一个返回图片尺寸的字典
    事实上此处你可以绑定任意对象。此处你绑定的对象既是回调方法中的参数ref

    好吧就然说到这我就直接把那三个回调方法说了吧,放在一起比较好理解一些。

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

    上文说过,ref既是创建代理是绑定的对象。所以我们在这里,从字典中分别取出图片的宽和高

    值得注意的是,由于是c的方法,所以也没有什么对象的概念。是一个指针类型的数据。不过oc的对象其实也就是c的结构体。我们可以通过类型转换获得oc中的字典。
    __bridge既是C的结构体转换成OC对象时需要的一个修饰词

    老司机敲字慢啊,敲到这都两个小时了,容我喝口水。

    你们喝过红色的尖叫么?老司机喝了那种烟头泡的水之后精神满满的继续敲字。(那水超难喝,你可以挑战一下)
    诶,说好的严肃呢?


    图片的插入

    首先创建一个富文本类型的图片占位符,绑定我们的代理

    unichar placeHolder = 0xFFFC;//创建空白字符
        NSString * placeHolderStr = [NSString stringWithCharacters:&placeHolder length:1];//已空白字符生成字符串
        NSMutableAttributedString * placeHolderAttrStr = [[NSMutableAttributedString alloc] initWithString:placeHolderStr];//用字符串初始化占位符的富文本
        CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderAttrStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);//给字符串中的范围中字符串设置代理
        CFRelease(delegate);//释放(__bridge进行C与OC数据类型的转换,C为非ARC,需要手动管理)
    

    这里富文本的知识上文中已经介绍过了。不过老司机猜你有三个疑问。

    • 这个添加属性的方法怎么是这个样子的?
      因为这里是添加CTRunDelegate这种数据类型,要用CoreText专门的方法,不过其实就是形式不同,作用一样的。
    • 为什么这里富文本类型转换的时候不用_bridge呢?老司机你不是说需要修饰词么?你是不是骗我?(markDown语法冲突我少打一个下划线)
      真没有,事实上不是所有数据转换的时候都需要__bridge。你要问我怎么区分?那好我告诉你,C中就是传递指针的数据就不用。比如说字符串,数组。原因老司机现在解释不通,等我能组织好语言的。
    • 为什么还要释放?我是ARC环境啊
      不好意思,我也是。不过为什么要释放呢?因为你进行了类型转换之后就不属于对象了,也不再归自动引用计数机制管理了,所以你得手动管理咯。

    然后将占位符插入到我们的富文本中

    [attributeStr insertAttributedString:placeHolderAttrStr atIndex:12];//将占位符插入原富文本
    

    此处我就不赘述了,富文本的知识你只要类比字典就好了。
    至此,我们已经生成好了我们要的带有图片信息的富文本了,接下来我们只要在画布上绘制出来这个富文本就好了。


    绘制

    绘制呢,又分成两部分,绘制文本绘制图片。你问我为什么还分成两个?

    因为富文本中你添加的图片只是一个带有图片尺寸的空白占位符啊,你绘制的时候他只会绘制出相应尺寸的空白占位符,所以什么也显示不了啊。
    那怎么显示图片啊?拿到占位符的坐标,在占位符的地方绘制相应大小的图片就好了。恩,说到这,图文混排的原理已经说完了。

    先来绘制文本吧。


    绘制文本
    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);//一个frame的工厂,负责生成frame
    CGMutablePathRef path = CGPathCreateMutable();//创建绘制区域
    CGPathAddRect(path, NULL, self.bounds);//添加绘制尺寸
    NSInteger length = attributeStr.length;
    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0,length), path, NULL);//工厂根据绘制区域及富文本(可选范围,多次设置)设置frame
    CTFrameDraw(frame, context);//根据frame绘制文字
    

    frameSetter是根据富文本生成的一个frame生成的工厂,你可以通过framesetter以及你想要绘制的富文本的范围获取该CTRun的frame
    但是你需要注意的是,获取的frame是仅绘制你所需要的那部分富文本的frame。即当前情况下,你绘制范围定为(10,1),那么你得到的尺寸是只绘制(10,1)的尺寸,他应该从屏幕左上角开始(因为你改变了坐标系),而不是当你绘制全部富文本时他该在的位置

    然后建立一会绘制的尺寸,实际上就是在指定你的绘制范围
    接着生成整个富文本绘制所需要的frame。因为范围是全部文本,所以获取的frame即为全部文本的frame(此处老司机希望你一定要搞清楚全部与指定范围获取的frame他们都是从左上角开始的,否则你会进入一个奇怪的误区,稍后会提到的)。
    最后,根据你获得的frame,绘制全部富文本


    绘制图片

    上面你已经绘制出文字,不过没有图片哦,接下来绘制图片。
    绘制图片用下面这个方法,通用的哦

    CGContextDrawImage(context,imgFrm, image.CGImage);//绘制图片
    

    我们可以看到这个方法有三个参数,分别是context,frame,以及image
    要什么就给他什么好咯,context和image都好说,context就是当前的上下文,最开始获得那个。image就是你要添加的那个图片,不过是CGImage类型。通过UIImage转出CGImage就好了,我们重点讲一下frame的获取。

    frame的获取

    记得我之前说的误区么?这里我们要获得Image的frame,你有没有想过我们的frameSetter?

    我也想过,不过就像我说的,你单独用frameSetter求出的image的frame是不正确的,那是只绘制image而得的坐标,所以哪种方法不能用哦,要用下面的方法。

    你们一定发现,我获取frame的方法单独写了一个方法,为什么呢?
    1.将代码分离,方便修改。
    2.最主要的是这部分代码到哪里都能用,达到复用效果。

    NSArray * arrLines = (NSArray *)CTFrameGetLines(frame);//根据frame获取需要绘制的线的数组
    NSInteger count = [arrLines count];//获取线的数量
    CGPoint points[count];//建立起点的数组(cgpoint类型为结构体,故用C语言的数组)
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), points);//获取起点
    

    第一句呢,获取绘制frame中的所有CTLine。CTLine,又不知道了吧,老司机又要无耻的盗图了。

    CTFrame组成
    上面呢,我们能看到一个CTFrame绘制的原理。
    • CTLine 可以看做Core Text绘制中的一行的对象 通过它可以获得当前行的line ascent,line descent ,line leading,还可以获得Line下的所有Glyph Runs
    • CTRun 或者叫做 Glyph Run,是一组共享想相同attributes(属性)的字形的集合体

    一个CTFrame有几个CTLine组成,有几行文字就有几行CTLine。一个CTLine有包含多个CTRun,一个CTRun是所有属性都相同的那部分富文本的绘制单元。所以CTRun是CTFrame的基本绘制单元
    接着说我们的代码。
    为什么我获取的数组需要进行类型转换呢?因为CTFrameGetLines()返回值是CFArrayRef类型的数据。就是一个c的数组类型吧,暂且先这么理解,所以需要转换。

    那为什么不用__bridge呢?记得么,我说过,本身就传地址的数据是不用桥接的。就是这样。
    然后获取数组的元素个数。有什么用呢,因为我们要用到每个CTLine的原点坐标进行计算。每个CTLine都有自己的origin。所以要生成一个相同元素个数的数组去盛放origin对象
    然后用CTFrameGetLineOrigins获取所有原点。
    到此,我们计算frame的准备工作完成了。才完成准备工作。


    计算frame

    思路呢,就是遍历我们的frame中的所有CTRun,检查他是不是我们绑定图片的那个,如果是,根据该CTRun所在CTLine的origin以及CTRun在CTLine中的横向偏移量计算出CTRun的原点,加上其尺寸即为该CTRun的尺寸。

    跟绕口令是的,不过就是这么个思路。

    for (int i = 0; i < count; i ++) {//遍历线的数组
            CTLineRef line = (__bridge CTLineRef)arrLines[i];
            NSArray * arrGlyphRun = (NSArray *)CTLineGetGlyphRuns(line);//获取GlyphRun数组(GlyphRun:高效的字符绘制方案)
            for (int j = 0; j < arrGlyphRun.count; j ++) {//遍历CTRun数组
                CTRunRef run = (__bridge CTRunRef)arrGlyphRun[j];//获取CTRun
                NSDictionary * attributes = (NSDictionary *)CTRunGetAttributes(run);//获取CTRun的属性
                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;//获取上距
                CGFloat descent;//获取下距
                CGRect boundsRun;//创建一个frame
                boundsRun.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
                boundsRun.size.height = ascent + descent;//取得高
                CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);//获取x偏移量
                boundsRun.origin.x = point.x + xOffset;//point是行起点位置,加上每个字的偏移量得到每个字的x
                boundsRun.origin.y = point.y - descent;//计算原点
                CGPathRef path = CTFrameGetPath(frame);//获取绘制区域
                CGRect colRect = CGPathGetBoundingBox(path);//获取剪裁区域边框
                CGRect imageBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
                return imageBounds;
    

    有了上面的思路这里就很好理解了。
    外层for循环呢,是为了取到所有的CTLine
    类型转换什么的我就不多说了,然后通过CTLineGetGlyphRuns获取一个CTLine中的所有CTRun
    里层for循环是检查每个CTRun。
    通过CTRunGetAttributes拿到该CTRun的所有属性
    通过kvc取得属性中的代理属性
    接下来判断代理属性是否为空。因为图片的占位符我们是绑定了代理的,而文字没有。以此区分文字和图片。
    如果代理不为空,通过CTRunDelegateGetRefCon取得生成代理时绑定的对象判断类型是否是我们绑定的类型,防止取得我们之前为其他的富文本绑定过代理
    如果两条都符合,ok,这就是我们要的那个CTRun
    开始计算该CTRun的frame吧。
    获取原点和获取宽高被。
    通过CTRunGetTypographicBounds取得宽,ascent和descent。有了上面的介绍我们应该知道图片的高度就是ascent+descent了吧。
    接下来获取原点。
    CTLineGetOffsetForStringIndex获取对应CTRun的X偏移量
    取得对应CTLine的原点的Y,减去图片的下边距才是图片的原点,这点应该很好理解。
    至此,我们已经获得了图片的frame了。因为只绑定了一个图片,所以直接return就好了,如果多张图片可以继续遍历返回数组。
    获取到图片的frame,我们就可以绘制图片了,用上面介绍的方法。


    哦,别忘了手动释放你创建的对象哦。

    CFRelease(frame);
    CFRelease(path);
    CFRelease(frameSetter);
    

    大功告成。


    好了,至此你已经完成图片的绘制了。只要在ViewController里面引入你绘制CoreText文本的View正常的初始化添加子视图就可以了。

    好吧,这个教程我也是综合了很多资料写出来的。优势是在于我一句一句讲的,几乎每一句都告诉你原理了吧。

    恩,我也是在前人的基础上自己总结查阅出来的,难免夹杂着个人理解和部分偏颇,如果各位看官发现我写的有什么不对的地方欢迎与我联系,老司机的邮箱codewicky@163.com

    原谅老司机逗逼的本质,严肃不起来。

    下面是一些参考资料:
    coreText方法,列的很全
    CTRun的详细介绍
    CTLine的详细介绍
    coreText基本原理及使用方法
    图文混排的整体介绍
    coreText使用方法

    你要是喜欢呢,麻烦你动一动你可爱的小手点击一下喜欢或者关注,毕竟老司机这么爱慕虚荣的人,而且老司机会经常更新的。

    最后,你问我为什么一直叫自己老司机?哦,因为嘿嘿嘿~~~

    哦,最后的最后,若果真有人转载的话,麻烦你注明出处。
    http://www.jianshu.com/p/6db3289fb05d

    相关文章

      网友评论

      • 箫声_筱昇:那么如何插入多张图片呢.我自己试了试.虽然说占位符插入的位置的正确.但是图片却没有插入到相应的位置,插入到了第一张图片的位置
        老司机Wicky:@箫声_筱昇 我的代码用起来没问题,你看看内部实现不就行了
        箫声_筱昇:@老司机Wicky 大神,那你知道怎么写吗?
        老司机Wicky:说明你绘制图片写错了呗
      • simplePXJ:请教一下,图片在左侧,右侧环绕文字设置的首行缩进不生效有没有解决方案
      • 林林林瑞彬:不愧老司机。逻辑清楚 通俗易懂
      • Bleiler:请问CTLineGetGlyphRuns()方法获取的CTRunRef列表的数量总是错误显示为1是什么原因
        老司机Wicky:@谷胜亚 CTRun是包含有共同属性的文字集合,不是有几个字就有一个run,取决于这一行中是否有多种不同属性的文字
      • 悟2023:问什么添加绘制尺寸 CGPathAddRect(path, NULL, self.bounds) 使用self.frame 绘制区域就不正确了?
      • Breezes:有个疑问,两行文字都是一起共有属性的富文本,这两行肯定是两个CTLine,那这两行算是一个CTRun吗,
        老司机Wicky:@yqy159 同一行内共同属性的富文本在一个CTRun中
      • 深水日月:不错不错.......
      • kunkunm:突然有一个问题,在绘制文本的过程中,字体大小是怎么在哪里设置的?相关的frameSetter,frame,都没有看到哪里获取到字体大小呀
        老司机Wicky:@anonymousCat 你看一下attributeString的用法吧
        kunkunm:@老司机Wicky NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"\n这里在测试图文混排,\n我是一个富文本"];
        初始化之后也没有找到设置字号的相关代码。是这样的,我现在已经照着这个思路实现了,但是自己回顾的时候突然发现没有设置字号的代码,然后一直没找到...。。。
        老司机Wicky:@anonymousCat attributeString里面设置的
      • Gary_Wong:为什么我觉得有点像唐巧和耀源的结合起来复制粘贴改的,但是开发人员的事儿,那能叫抄么:relieved:
      • 骑老虎喊救命:重写了drawRect 会有离屏渲染吧,这样性能还会很好吗
      • fengshaobo:你真棒
      • f2efa87f6528:求demo,谢谢老司机 18566103823@163.com
        f2efa87f6528:@老司机Wicky 好的,谢谢!
        老司机Wicky:@晓楼昨夜听风雨 第三篇文章末尾
      • mmmickychiang7:大神 是不是 也应该写上CFRelease(context);
        mmmickychiang7:@老司机Wicky 谢谢= =
        老司机Wicky:@巴里Micky的小短腿们 drawRect中UIGraphicsGetCurrentContext()获取的context是系统栈维护的,不需要显示调用release。
        mmmickychiang7:我可能明白了...是不是因为不是create 所以不用release...
      • 孙掌门:写的很好很详细很用心,但是老司机你是不是飘了,好多都不在主线程处理,会出问题的,例如在子线程中异步绘制的时候,里面有self.bounds之类的 都是不行的,这么完美的产品不能因为这些细节打你脸啊,哈哈,有时间你改一下,就完美了。
        老司机Wicky:@TyperMan丶 都是因为镜像,画个图就明白啦
        孙掌门:@老司机Wicky 我建议在.m中简历一个私有变脸 然后在initWIthFrame的时候,给这个私有变量复制就可以,包括self.subviews之类的,复制给这个私有变量,其余的地方拿来用就可以了,哈哈,我已经改好了,最近我在研究你这个Demo,哈哈,有两个地方不明白,就是CGRect frame = CGRectMake(self.textInsets.left, self.textInsets.bottom, width, height);,为什么穿left和bottom,还有这个方法static void convertPath(UIBezierPath *path , CGRect frame){
        [path applyTransform:CGAffineTransformMakeScale(1, -1)];
        [path applyTransform:CGAffineTransformMakeTranslation(0, 2 * frame.origin.y + frame.size.height)];
        },为什么是2 * frame.origin.y + frame.size.height,想了半天没有想明白.
        老司机Wicky:@TyperMan丶 考虑过改那个警告:smile:没想出来咋改,然后就忽略了那个警告,哈哈
      • 维维豆奶1991:取得对应CTLine的原点的Y,减去图片的下边距才是图片的原点 CTLine的原点在哪???
      • 维维豆奶1991:你单独用frameSetter求出的image的frame是不正确的,那是只绘制image而得的坐标,所以哪种方法不能用哦。。。。。哪种方法
      • 维维豆奶1991:CGFloat ascent;
        CGFloat descent;
        CGRect boundsRun;
        boundsRun.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
        boundsRun.size.height = ascent + descent;
        CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
        boundsRun.origin.x = point.x + xOffset;
        boundsRun.origin.y = point.y - descent;
        CGPathRef path = CTFrameGetPath(frame);
        CGRect colRect = CGPathGetBoundingBox(path);
        CGRect imageBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
        这个计算坐标看不懂啊。。。。。。
      • 小学生课代表:老司机,能否赐我个demo...刚在学习这个,非常感谢您:smile:
        小学生课代表:@老司机Wicky 抱歉,写评论的时候就大概看了一眼,:no_mouth: 一开始我只是想找到我想了解的一个问题,所以就大致看了一眼,刚看完,我还是有那个问题,就是 CTLineRef line = (__bridge CTLineRef)arrLines[i];
        NSArray * arrGlyphRun = (NSArray *)CTLineGetGlyphRuns(line);这两句得到的应该是第i行的所有字符的数组是么?
        但是arrGlyphRun在我自己的demo打印出来的值却跟我实际显示的那一行有几个字符对不上
        老司机Wicky:@先想一个符合我的笔名再改 请问你这篇文章读了么?读了你应该就不会管我要demo了
        小学生课代表:忘了留邮箱了....fangchunyang1016@163.com
      • pro_cookies:old driver 听说出了demo嘞 想求一份 627610861@qq.com
        pro_cookies:@老司机Wicky 哦哦,好的,没注意,我去看一下
        老司机Wicky:@pro_cookies 文章中有说demo在第三篇文章中
      • 狗狗臭鸡蛋:CTRunRef 写前置声明的时候,直接写CTRunRef runCurrent = nil;也应该可以的阿扁
      • UNforgetta_ab44:球demo 老司机 我的邮箱949903006@qq.com
        UNforgetta_ab44:@老司机Wicky Thank you
        老司机Wicky:@UNforgetta_ab44 第三篇文章里面有
      • 哈哈哈我的简书账号:您好问一下您,CGMutablePathRef path = CGPathCreateMutable();
        CGPathAddRect(path, NULL, self.bounds);,这里边self的frame怎么设置呢?现在图文混排没法计算文本占据的高度啊
        哈哈哈我的简书账号:@老司机Wicky 您之前说图文混排有三种思路,
        UIKit
        Core Graphics/Quartz
        Core Text ,
        那么quartz 2d 怎么图文混排呢?您可以给我个思路嘛,在网上找不讲quartz 2d 图文混排到资料,全是cortext的实现,多谢您啦
        (例如:对于图文混排文本:hello world,然后后边跟着一张图片1.png ,我们需要在for循环里一个单词一个单词的绘制嘛)
        哈哈哈我的简书账号:@老司机Wicky 谢谢您哈,您写的文章真心很好
        老司机Wicky:CTFramesetterSuggestFrameSizeWithConstraints这个函数可以计算尺寸
      • liangdahong::joy: 这样的blog看起来才精神百倍。
      • 神采飞扬_2015:文风口水话太多哈:smile:
      • 疯狂的小码农:Mark求demo 164793094@qq.com
        疯狂的小码农:@老司机Wicky 好的 谢谢你
        老司机Wicky:@疯狂的小码农 文章开头有写啊,Demo在第三篇文章末尾啊
      • 6ddc32fc6c6a:图片混排时,图片是从服务器获取的,好实现吗
        老司机Wicky:@剑客_a9c5 异步获取,然后绘制
      • ifelseboyxx:老司机 什么叫 “CGContextSetTextMatrix(context, CGAffineTransformIdentity);//设置字形的变换矩阵为不做图形变换 ” 能形象的解释一波么?
        ifelseboyxx:@老司机Wicky 原来如此,谢谢
        老司机Wicky:@JohnsLee 首先上面做了矩阵变换,导致所有绘制的图形相当于都做了x轴的镜像。这时候我们会发现绘制的文字也被镜像了,所以我们要让文字不做矩阵变换
      • a1384d208cf1:大神你好,请教个问题,CTRun 设置了delegate
        在CTFramesetterCreateWithAttributedString()的时候会执行回调方法:
        ascentCallback(void *ref) 但这里一直提示EXC_BAD_ACCESS,
        debug只有-[__NSDictionaryI objectForKey:]: message sent to deallocated instance ,
        ref只是一个地址,排查到这里不知道怎么排了,求助啊大神
      • 云画的跃光:老司机,我报这种错,是啥意思?
        <Error>: CGContextSetTextMatrix: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
        老司机Wicky:@云画的跃光 第三篇开头
        云画的跃光:@老司机Wicky 可以发demo看一下吗?15889791849@163.com
        老司机Wicky:你的上下文是空的
      • Wang66:不错不错,先mark后学习。之前想自己用CoreText搞个图文混排的小控件,卡住半途而废,最终只用NSTextAttachment简单实现了。
      • 9d3816c22292:不明白下面三行的作用,不是已经计算好了图片的坐标了吗,加下面三行是为了什么
        CGPathRef path = CTFrameGetPath(frame);//获取绘制区域
        CGRect colRect = CGPathGetBoundingBox(path);//获取剪裁区域边框
        CGRect imageBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
        老司机Wicky:@霾霾 坐标转换。boundsRun获取到的是当前CTRun相对于当前绘制path的坐标,要加上path本身的原点才是屏幕的坐标系统:stuck_out_tongue_winking_eye:
      • 难却却:厉害了
      • da27c260cc85:不错,可以配合着学习yykit
      • o0阿拉斯加的狗0o:“UIKit 的 UILabel 允许你通过在 IB 中简单的拖曳添加文本,但你不能改变文本的颜色和其中的单词。”这句话,UILabel可以改变文本的颜色啊,“label.text”可以改变其中的文字,“label.textColor”可以修改文字的颜色啊,还是我理解错了?谢谢了
        来扶爷试玩个波:@o0阿拉斯加的狗0o 准确点翻译是,不能单独修改个别字的颜色
        o0阿拉斯加的狗0o:@老司机Wicky 哦,了解了,三克油:sweat_smile:
        老司机Wicky:@o0一路向北0o In UIKit you have UILabel and you add a word or a text line on the screen by simple Drag-and-Drop in IB, but you cannot change the color of individual words.

        以上是这段话的原文。这段话摘自
        https://www.raywenderlich.com/4147/core-text-tutorial-for-ios-making-a-magazine-app
        文章写于2011年,当时系统为iOS3.2,UILabel的attributedText是iOS6.0以后新增属性。
        故当时的环境下还不能修改个别单词的颜色。
        恩,就是这么一个背景
      • 千炼成钢:邮箱地址:asdfgwjm@163.com :sweat: 。忘了说地址了。
        老司机Wicky:@千炼成钢 第三篇末尾有一个我开源的控件,求star
        千炼成钢:@老司机Wicky 👌,学习了!已关注
        老司机Wicky:@千炼成钢 第三篇文章末尾有demo的:sweat:
      • 千炼成钢:老司机,求demo,谢谢! :smile:
        老司机Wicky:@千炼成钢 demo在第三篇文章末尾哈:smile:
      • Springer:参考资料最起码也要加上这个吧? https://my.oschina.net/megan/blog/269042
        老司机Wicky:@Springer 当时查阅的资料很多,也不只我列出的几个资料,有很多资料链接也在过程中丢失。抱歉,我这就补上。
      • fa32e02f48b0:老司机 你简直是吊炸天,请收下我的膝盖!!
      • 7d30e59e1b8e:楼主测试这个图文混排可以用嘛
        7d30e59e1b8e:@晓楼昨夜听风雨 感谢回复
        f2efa87f6528:求demo,谢谢老司机 18566103823@163.com
        老司机Wicky:@如如火 可以啊
      • HenryM:请问下: "减去图片的下边距才是图片的原点"是因为CTLine的origin在左下角吗?
        老司机Wicky:@maliao 嗯哪,看字形那张图片
      • 念念不忘2014:如果用autoLayout 设置 约束, 不能显示。必须设置个高度,不知道该如何再不设置高度的情况下让它自适应高度?
        老司机Wicky:@念念不忘2014 所以你只要在合适的地方,(比如说layoutSubviews)layoutSubviews里面让他重新绘制,不是么
        念念不忘2014:@老司机Wicky 如果在使用autolayout 设置top,leading, tralling,不设置height或者bottom, drawrect 方法不会调用,因为此时,rect 缺少元素,没办法时一个矩形
        老司机Wicky:@念念不忘2014 你在drawrect中打个断点,看看rect的值:smile:
      • yml7822:这个方法和iOS提供的富文本功能好像比较类似吧。
        如果要做一个字环绕图片的,应该怎么做呢?
        老司机Wicky:@yml7822 该效果请通过textKit实现
      • ethan_cun:果然老司机
      • babystyles:求Demo一看 谢谢老司机 652218615@qq.com
        老司机Wicky:@这一翅蓝瘦香菇 已发送
      • 流动的水gita:求demo,573702745@qq.com
        流动的水gita:@老司机Wicky 感谢
        老司机Wicky:@流动的水gita 已发送
      • yml7822:请问你有github吗?请一个demo,我想调试看程序。
        邮件lym_ay@sina.com,谢谢啊
        yml7822:@老司机Wicky 谢谢啊
        老司机Wicky:@yml7822 已发送
      • zwing:求个demo.大神!207764267@qq.com
        zwing:@老司机Wicky 谢谢啦!
        老司机Wicky:@weiaini 已发送
      • zwing:求个demo!大神!:blush::blush::blush:
      • 老司机Wicky:已发送
      • 小呵呵呵:写的很好,很详细,求个demo 572881873@qq.com 谢谢了
      • initial_J:如果是在cell上, 这样操作,调用drawRect 会不会卡的飞起?
      • 1db7375f24de:求demo,1849811416@qq.com :grin:
        老司机Wicky:@xiao_jun 已发送
      • 夜不知枫:写的很认真啊哈哈哈哈,给车票就更好了
      • 仲博:大神-_-有demo吗?邮箱2542012034@qq.com 谢谢
        老司机Wicky:@仲博 已发送
      • 乜_啊_:很喜欢这篇文章,感谢老司机带路。
        老司机Wicky:@维维豆奶1991 请看标题为字形的配图
        维维豆奶1991:取得对应CTLine的原点的Y,减去图片的下边距才是图片的原点,这怎么理解??望解答。。谢谢
        老司机Wicky: @乜_啊_ 多谢支持
      • bf9bb0cce5eb:求demo啊 @老司机Wicky 万分感谢 :pray: :pray: 邮箱:zyn430@163.com
        老司机Wicky:@曾经沧海丶 已发送
      • 646a038a2851:感谢您的文章,您能给我发一份demo吗,万分感谢!我的邮箱:1492754476@qq.com 谢谢
        646a038a2851:@646a038a2851 感谢
        老司机Wicky:@646a038a2851 已发送
      • 清晨十分的阳光:跪求Demo 老司机~1067926162@qq.com
        清晨十分的阳光:@老司机Wicky 十分感谢:pray:
        老司机Wicky:@清晨十分的阳光 已发送
      • 13726222894:求Demo, 点击和混排的 , 845735925@qq.com,谢谢
        老司机Wicky: @13726222894 已发送
      • So1dier:老司机发个Demo。 245184907@qq.com
        老司机Wicky:@So1dier 已发送
      • colbert02:老司机同志,给个demo呗 392066541@qq.com
        colbert02:@老司机Wicky 谢谢了
        老司机Wicky:@colbert02 已发送
      • 32b37300e729:有一点小疑惑,如果只是简单的图文混排,NSMutableAttributeString也可以做。CoreText看着好绕啊。。iOS8之后也有WKWebView,内存占用只有UIWebView的1/3左右(现在应该大部分App是最低适配8.0的吧)
      • Clemo:mark
      • 我的大名叫小爱:幽默风趣啊.
      • 21593bb673fb:老司机,demo在哪下载?179434316@qq.com,万分感谢!!!! :yum:
        21593bb673fb:@老司机Wicky 方便发一下你的QQ吗,想咨询您一下
        老司机Wicky:@进击的小白 已发送
        21593bb673fb:@进击的小白 能留一个联系方式吗》想和您沟通一下技术
      • 尐情緒:老死机,求个Demo 邮箱:395817333@qq.com
        老司机Wicky:@尐情緒 已发送
        尐情緒:@老司机Wicky 打错字了,老司机,见谅,百度输入法坑我。
        老司机Wicky:@尐情緒 别这样,慎得慌
      • 无畏009:cjlsire@126.com 跪求一份demo
        无畏009:@老司机Wicky 收到:smile:
        老司机Wicky:@陈建蕾 已发送
      • a4c252c4ea10:多个图片怎么绘制? 或者有demo么 能给个链接不? 或者yangzhwork@126.com :kissing_heart:
        a4c252c4ea10:@老司机Wicky 非常感谢博主 , 你的列子中已经给出来了! 感谢噢
        a4c252c4ea10:@老司机Wicky 嗯谢谢博主了, 昨天也是弄出了单个的, 想自己封装个label 但后来想想需要多个图片 然后就是一直卡在这个计算frame的方法上了, 那我今天再自己研究研究, 非常感谢.
        老司机Wicky:@AppariTT 多个图片就多个占位符,计算frame的算法需要稍作修改,然后分别绘制图片就好了,跟单个图片区别不大。我这手边多个图片的demo消失在历史的长河中了,只有单个图片的。区别不大,不参考下,已发送
      • Caolongs:老司机,求份Demo研究 caolongs231@gmail.com
        老司机Wicky:@武阳小生 文中有黑体字
        武阳小生:什么改动啊,老司机。
        Caolongs:文字点击事件。。。
      • yourbigbug:非常好,能告诉我那个改动是什么吗?
        yourbigbug:@老司机Wicky :+1::+1::+1:谢谢哈
        老司机Wicky:@8ab5dcef3eb2 文中有用超大字指出来
      • mysteryemm:666不愧是老司机,学习了:smiley:
      • 空恋的爱:可以借鉴一下demo?
      • nuannuan_nuan:你好,看了下亲的文章,幽默诙谐,感觉很通熟易懂呀,之前还以为coreText很难,被亲这么详细解释下还真的很好理解,不胜感激。但是还有一处没有明白:
        “CGRect colRect = CGPathGetBoundingBox(path);//获取剪裁区域边框
        CGRect imageBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);”
        这里为什么会用到裁剪区域?

        另外demo为什么不给一个公开连接而是私信发邮箱呀,我们相信开源的力量 :stuck_out_tongue_closed_eyes: :stuck_out_tongue_closed_eyes:
        老司机Wicky:@nuannuan_nuan 重复绘制问题,这就是个uiview,你说能不能呢?第二,你对cell重用理解错了,自行百度吧,第三,你可以自己写一个类,通过传参初始化,传完参数绘制。当然只要绘制了就不能更改了
        nuannuan_nuan:@老司机Wicky 好的,谢谢你的解答 :smile: 。还有个问题想要咨询一下,如果要将图文混牌绘制应用到cell上可以实现吗。个人感觉图文的一次性绘制和cell的重用是矛盾的,上面的demo中的NSMutableAttributedString和UIIamge都是在drawInRect方法中创建的,而实际应用的情况大多是外部给予赋值,也就是说外部赋值的话必须在UIView调用drawInRect方法之前处理好NSMutableAttributedString和UIIamge。所以对于coreText能够重复绘制吗?
        老司机Wicky:@nuannuan_nuan 因为我觉得看别人的东西没有自己敲一遍来的实际所以我不是很鼓励要demo,自己敲一遍或许会有更深的印象。
        至于获取裁剪区域真的是因为写习惯了。
        这句的意义何在呢?
        首先CoreText做图文混排只是他的一种用途,他还有跟绘制有关的很多作用。
        在一个drawRect中根据绘制需要可能会有很多的CTFrame。那么就会出现我们创建的CTFrame并不是以origin为(0,0)的点去创建的CTFrame。而CTLine及CTRun相关计算方法所求得的坐标都是以对应CTFrame的原点为基础计算的。
        因此,当我们获取CTRun针对CTFrame的坐标后,要校正CTFrame的偏移量。
        我们通过获取剪裁区域拿到CTFrame的偏移量从而进行校正。

        然而在本例中CTFrame是以self.bounds创建的,所以也没有必要进行校正。
        由于写demo的时候没有考虑多个绘制区域所以直接用的self.bounds。而后又顺手写了校正偏移并没有做具体解释。

        如果你想仔细体会,生成frame的时候自己给个origin不是(0,0)的rect就可以了。
      • 鼻毛长长:上下文是什么= =
        老司机Wicky:@鼻毛长长 承载当前环境属性的数据载体
      • 我是C: CFRelease(path);
        CFRelease(frameSetter);
        CFRelease(frame);
        写三个就得crash 后两个只能写一个, 是不是cf 对象都得CFRelease呢?
        老司机Wicky:@GreyLove 哦,我发现了,因为demo里面我frame没释放,所以没问题。我看看怎么回事啊,不过CF的都是放绝对是对的
        我是C:@老司机Wicky 1059395623@qq.com
        老司机Wicky: @GreyLove 我这没问题啊,给个邮箱传你个demo(๑•̀ㅂ•́)و✧
      • mirrorzyb:老司机可以发个demo吗?Mirrorzyb@outlook.com不胜感激!
        老司机Wicky:@老司机Wicky 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
        老司机Wicky: @mirrorZyb demo已发,不过这demo要比简书早一些,写简书的时候做过修改。还是建议照着老司机的思路自己写一遍,熟悉一下。
      • 程序猴子:第一次搞 同需Demo一份 911029641@qq.com 谢谢、
        老司机Wicky:@蟹蟹猿 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
        程序猴子:@老司机Wicky 晓的了、抱拳
        老司机Wicky:@蟹蟹猿 其实我更鼓励你敲一份,这也是为什么不发demo链接的原因。demo已发。
      • 4e729828863e:1913922998@qq.com要demo谢谢
        老司机Wicky:@老司机Wicky 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
        老司机Wicky:@李科强 已发送
      • 2e0084e1e76a:该怎么使用 直接创建的uiview 重写drawrect方法吗? 然后按照你说的在vc里创建这个view 显示的是一块黑色区域没有文字 有Demo吗? 表示第一次使用CoreText。。。。。。
        老司机Wicky:@梦断oo花都 已发送
        2e0084e1e76a:@老司机Wicky 1812869215@qq.com 谢谢 麻烦你了!
        老司机Wicky:@梦断oo花都 给个邮箱
      • 若锦:好棒,前两天还在找图文混排的,可惜没看到这篇,哈哈,然后用的YYText.今晚在蓝鸥的公众号上看到了你写的这文章,点进来看了原文,果断收藏了
        老司机Wicky:@雨汐流若 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • 芒果味的:6666
        老司机Wicky:@芒果味的 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • STDawn:mark
        老司机Wicky:@ST_Dawn 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • 萧城x:UIKit 的 UILabel 允许你通过在 IB 中简单的拖曳添加文本,但你不能改变文本的颜色和其中的单词。
        啥意思 label可以settitleColor啊
        老司机Wicky:@cjckkk html,实现起来很简单,不过有一个缺点就是在iOS上WebView的内存问题,真的是很头疼的。但是也有相应的优点就是html可以实现跨平台。但从iOS和性能方面考虑还是CoreText好一些
        萧城x:@老司机Wicky 哦哦 你觉得图文并排的实现 通过html效果好 还是coretext比较好
        老司机Wicky:@cjckkk 意思就是UILabel的text只能设置text的全部字符串的属性,不能分段设置,不能单独设置一部分,所以由attributedText中赋值符文本去对其中一部分文字进行单独设置属性
      • 丶Destinyxl:为老师傅的耐心点个赞 :+1:
        老司机Wicky:@GCC_欧了个耶 你绑定的字典已经释放了,看看是不是设置代理的时候有什么错误
        a1384d208cf1:大神你好,请教个问题,CTRun 设置了delegate
        在CTFramesetterCreateWithAttributedString()的时候会执行回调方法:
        ascentCallback(void *ref) 但这里一直提示EXC_BAD_ACCESS,
        debug只有-[__NSDictionaryI objectForKey:]: message sent to deallocated instance ,
        ref只是一个地址,排查到这里不知道怎么排了,求助啊大神
        老司机Wicky:@LL_Summit 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • 陳曉萌:收藏了,虽然不知道什么时候会用到。
        老司机Wicky:@陳曉萌 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • 栀厄:滴滴滴,学生卡,老司机求demo
        老司机Wicky:@栀厄 之前没注意,留个邮箱,代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • 给你快乐:正好搭上顺风车了!!!
        老司机Wicky:@给你快乐 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • qBryant:拜读老司机的文章!
        老司机Wicky:@QBryant 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • 1d1146c7943d:谢谢老司机
        老司机Wicky:@isisisForever 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • husky_duang:丁丁?
        老司机Wicky:@固执的 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • 小白也可爱:滴学生卡! 毕竟老司机,厉害
        老司机Wicky:@小白也可爱 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • 努力奔跑的H先生:上车上车
        老司机Wicky:@努力奔跑的H先生 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • Wang66:写得很清晰,先收藏了,下来再好好研究。
        Wang66:@老司机Wicky 谢谢😜
        老司机Wicky:@Wang66 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • YYLittleCat:小学生上车了,老司机请等一下,,,,刷卡中,,,,,
        老司机Wicky:@hahhahahahahah 别闹了,大哥
        YYLittleCat:@老司机Wicky 请你猜猜我是谁
        老司机Wicky:@hahhahahahahah 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • 浅月堂前: :+1:
        老司机Wicky:@浅月堂前 代码有重要改动,之前存心遗漏一句,为此对您造成的困扰深表歉意。请务必关注修改的地方,文中有明显的提示。
      • captainGao:可以实现图文的编辑功能吗
        老司机Wicky:@俊轩 是的,因为上面的demo是在view的drawRect里面直接绘制了。事实上你可以将这些功能拆分开来,一直到生成富文本之前单独抽出一个类。与一个view做关联,让view绘制富文本。每次你更改富文本的时候重新绘制一个view替换掉原来的view。这里要说明一下,不是说你改富文本后view就会改变。已经绘制好了view不会改变的,需要重新绘制并替换的。
        captainGao:@老司机Wicky 就是来回的更改内容,来回的重新绘制?不知道理解的对不对
        老司机Wicky:@俊轩 理论上如果开始绘制了就无法更改了,可以考虑控制富文本字符串后进行重新绘制实现编辑功能

      本文标题:CoreText实现图文混排

      本文链接:https://www.haomeiwen.com/subject/yzfmrttx.html