CoreText实现图文混排

来源:互联网 发布:淘宝售后退款不提醒 编辑:程序博客网 时间:2024/05/07 02:12

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 ;}
0 0