CoreText实现图文混排

IOS CoreText.framework — 基本用法
http://blog.csdn.net/fengsh998/article/details/8691823

IOS CoreText.framework — 段落样子CTParagraphStyle
http://blog.csdn.net/fengsh998/article/details/8700627

IOS CoreText.framework — 行 CTLineRef
http://blog.csdn.net/fengsh998/article/details/8701738

IOS CoreText.framework — 图文混排
http://blog.csdn.net/fengsh998/article/details/8714497

学习完了上述四篇博文准备做一个使用CoreText实现的图文混排的案例
由于CoreText采用的是底层的绘制方法,所以案例的实现要放在draw方法中进行实现
创建一个自定义视图CommonDetailView继承自UIView
在CommonDetailView.m文件中重写- (void)drawRect:(CGRect)rect;
- (void)drawRect:(CGRect)rect的调用只有在使用到此类的实例时才会调用
比如

CommonDetailView *cv =[[CommonDetailView alloc] init];
//此时drawRect并不会调用

只有当

[self.view addSubview:cv];
//此时调用CommonDetailView的drawRect方法

这其实是视图的加载时的一种懒加载方式

下面开始重写drawRect方法

- (void)drawRect:(CGRect)rect {   
    //得到绘图上下文对象(上下文是一种属性的有序序列,它们为驻留在环境内的对象定义环境。在对象的激活过程中创建上下文,对象被配置为要求某些自动服务,如同步、事务、实时激活、安全性等等)
    CGContextRef context = UIGraphicsGetCurrentContext();

    //设置字形变换矩阵为CGAffineTransformIdentity,也就是说每一个字形都不做图形变换
    //    CGContextSetTextMatrix(context, CGAffineTransformIdentity);

    //CGAffineTransformMake(CGFloat a, CGFloat b, CGFloat c, CGFloat d,CGFloat tx, CGFloat ty)
    //ad缩放,bc旋转,tx,ty位移
    CGAffineTransform flipVertical = CGAffineTransformMake(1,0,0,-1,0,self.bounds.size.height);

    //将当前context的坐标系进行翻转(如果没有将坐标系进行翻转,则绘制结果会倒置)
    CGContextConcatCTM(context, flipVertical);

    //创建需要绘制的文本
    //标题
    NSString *str = @"第一段:由触控科技主办的《Cocos 2015开发者大会(春季)》将于4月2日正式召开。作为当前市场占有率最高的开源手游引擎,今年的cocos开发者大会吸引了大批媒体和从业者的关注。门票一经发售,即引发了抢购热潮。第二段:为了满足不同参会者的需求,此次官方贴心地设置了多种梯队门票,包括免费票、个人票、团体票及VIP众筹门票。据悉,目前距离大会仍有约两周的时间,门票销量已经超过一半,还没有购票的小伙伴们要抓紧时间啦!";

//创建属性字符串
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]initWithString:str];
//对此段文本使用的字体属性
    UIFont *font = [UIFont fontWithName:nil size:kTitleFontSize];
    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)nil, font.pointSize, nil);
//为属性文本添加字体属性
    [attributedString addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)fontRef range:NSMakeRange(0, str.length)];
    CFRelease(fontRef);

    //为图片设置CTRunDelegate,delegate决定留给图片的空间大小等信息
    //所有的代理方法不能使用外部成员变量,只能使用本身的参数
        CTRunDelegateCallbacks imageCallbacks;
        imageCallbacks.version = kCTRunDelegateCurrentVersion;
        imageCallbacks.dealloc = RunDelegateDeallocCallback;
        imageCallbacks.getAscent = RunDelegateGetAscentCallback;
        imageCallbacks.getDescent = RunDelegateGetDescentCallback;
        imageCallbacks.getWidth = RunDelegateGetWidthCallback;

        //创建CTRunDelegateRef
        UIImage *image = [UIImage imageWithData:[[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:@"https://ss0.bdstatic.com/5a21bjqh_Q23odCf/static/superplus/img/logo_white_ee663702.png"]]];
        CTRunDelegateRef runDelegate = CTRunDelegateCreate(&imageCallbacks, (__bridge void *)(image));

        //创建需要绘制的图片的占位文本字符串
        NSMutableAttributedString *imageAttributedString = [[NSMutableAttributedString alloc] initWithString:@" "];//空格用于给图片留位置

        //为占位文本添加代理属性(即决定要绘制时的属性:如图片的宽高等),绘制时便会自动调用代理方法,为图片留出空间
        //NSMakeRange(0, 1)表示从文本的0位置往后1个字符将会用图片进行替换
        [imageAttributedString addAttribute:(NSString *)kCTRunDelegateAttributeName value:(__bridge id)runDelegate range:NSMakeRange(0, 1)];

        //释放CTRunDelegateRef
//        CFRelease(runDelegate);

        //为占位文本添加属性(即决定要绘制时的属性)
        //这里添加的属性是要绘制的图片
         NSString *imgName = [NSString stringWithFormat:@"https://ss0.bdstatic.com/5a21bjqh_Q23odCf/static/superplus/img/logo_white_ee663702.png"];
         //在属性字符串0-1的位置添加属性,值为图片的地址
        [imageAttributedString addAttribute:@"imageName" value:imgName range:NSMakeRange(0, 1)];

        //将占位文本插入到全局要绘制的属性字符串的某个位置处(这里就添加到第二段文字前)
        NSRange range = [str rangeOfString:@"第二段"];
        [attributedString insertAttributedString:imageAttributedString atIndex:NSMakeRange(0, rang.location)];

    //设置段落格式
    //换行模式
    CTLineBreakMode lineBreak = kCTLineBreakByCharWrapping;
    CTParagraphStyleSetting lineBreakMode;
    lineBreakMode.spec = kCTParagraphStyleSpecifierLineBreakMode;
    lineBreakMode.value = &lineBreak;
    lineBreakMode.valueSize = sizeof(CTLineBreakMode);

    //首行缩进若干坐标点
    CGFloat fristlineindent = 10.0f;
    CTParagraphStyleSetting fristline;
    fristline.spec = kCTParagraphStyleSpecifierFirstLineHeadIndent;
    fristline.value = &fristlineindent;
    fristline.valueSize = sizeof(float);

    //最大行高
    CGFloat _linespace = 5.0f;
    CTParagraphStyleSetting lineSpaceSetting;
    lineSpaceSetting.spec = kCTParagraphStyleSpecifierLineSpacing;
    lineSpaceSetting.value = &_linespace;
    lineSpaceSetting.valueSize = sizeof(float);
    CTParagraphStyleSetting settings[] = {
        lineBreakMode,
        fristline,
        lineSpaceSetting,
    };

    //段落格式属性组
    CTParagraphStyleRef style = CTParagraphStyleCreate(settings, 3);

    //将段落格式属性组转成字典
    NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithObject:(__bridge id)style forKey:(id)kCTParagraphStyleAttributeName ];

    CFRelease(style);

    //全局绘制信息添加段落格式属性组
    [attributedString addAttributes:attributes range:NSMakeRange(0, [attributedString length])];

    //CTFramesetter是CTFrame对象工厂,而一个CTFrame就是一个段落,一个段落包含多个行CTLine,一行包括多个分割CTRun
    CTFramesetterRef ctFramesetter = CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef)attributedString);

    //创建图形绘制句柄(即绘制路径)
    CGMutablePathRef path = CGPathCreateMutable();

    //绘制区域
    CGRect bounds = CGRectMake(0, -10, 320, 500);

    //添加绘制区域到绘制句柄(之后通过上下文来进行绘制)
    CGPathAddRect(path, NULL, bounds);

    //从CTFramesetter工厂中生产CTFrame对象(分割段落)CFRangeMake表示范围
    CTFrameRef ctFrame = CTFramesetterCreateFrame(ctFramesetter,CFRangeMake(0,str.length), path, NULL);

    //开始绘制(会回调代理方法,为图片留出区域)
    CTFrameDraw(ctFrame, context);

    //绘制图片=======================================================================================
    //得到所有绘制行
    CFArrayRef lines = CTFrameGetLines(ctFrame);

    MyLog(@"总行数%ld",CFArrayGetCount(lines));
    //CFArrayGetCount(lines):得到绘制的行数
    CGPoint lineOrigins[CFArrayGetCount(lines)];

    //得到行原点 也就是ctFrame从哪开始,
    CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), lineOrigins);

    //循环遍历所有绘制行
    for (int i = 0; i < CFArrayGetCount(lines); i++) {
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        CGFloat lineAscent;//原点上行线高度
        CGFloat lineDescent;//原点下行线高度
        CGFloat lineLeading;//行距
        //CTLineGetTypographicBounds:计算排版的属性赋值
        CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);

//PS:此时行的高度 =  lineAscent +  lineDescent +  lineLeading;    

//        MyLog(@"行=%d,lineAscent=%f,lineDescent=%f",i,lineAscent,lineDescent);

        //获取每一行的CTRun数组(CTRun的分割规则是,根据标点符号或者图片进行分割)
        CFArrayRef runs = CTLineGetGlyphRuns(line);

        for (int j = 0; j < CFArrayGetCount(runs); j++) {
            CGFloat runAscent;
            CGFloat runDescent;

            //获取CTRun原点
            CGPoint lineOrigin = lineOrigins[i];

            //获取CTRun
            CTRunRef run = CFArrayGetValueAtIndex(runs, j);

            //得到给CTRun设置的属性
            NSDictionary* attributes = (NSDictionary *)CTRunGetAttributes(run);

            //此处runRect保存的是图片所在行的rect
            CGRect runRect;

            //CTLineGetTypographicBounds计算排版的属性赋值
            runRect.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0,0), &runAscent, &runDescent, NULL);


            runRect=CGRectMake(lineOrigin.x + CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL), lineOrigin.y - runDescent, runRect.size.width, runAscent + runDescent);

            //从属性字典中取出图片地址
            NSString *imageName = [attributes objectForKey:@"imageName"];

            //图片渲染逻辑
            if (imageName) {
                //                UIImage *image = [UIImage imageNamed:imageName];
                NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@",imageName]];

                //下载图片
                UIImage *image = [UIImage imageWithData:[[NSData alloc] initWithContentsOfURL:url]];

                if (image) {

                    //设置绘制的图片区域
                    CGRect imageDrawRect;
                    imageDrawRect.size.width = 320  * 0.2;
                    imageDrawRect.size.height = 320  * 0.2;
                    //                    imageDrawRect.origin.x = runRect.origin.x + lineOrigin.x - 24;
                    imageDrawRect.origin.x = (320  - 320 * 0.2) / 2;
                    imageDrawRect.origin.y = runRect.origin.y;

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


                }
            }
        }
    }

    CFRelease(ctFramesetter);
    CFRelease(path);
    CFRelease(ctFrame);
}


#pragma mark -为要绘制的图片留出位置
void RunDelegateDeallocCallback( void* refCon ){

}

//控制留出区域的上行高度(一行的原点距离其最顶部的距离)
CGFloat RunDelegateGetAscentCallback( void *refCon ){

    return 320 * 0.2;

}

CGFloat RunDelegateGetDescentCallback(void *refCon){
    return 10 * 2;
}

//控制留出区域的宽度
CGFloat RunDelegateGetWidthCallback(void *refCon){
    return 320 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值