019-谈谈iOS 图文混排

一句话:画path,用Textkit;或者Core Text留位置。

iOS开发中要实现图文混排,简单从API层面看,有两种方法:
(1)用TextKit,设置一个path,然后文字会围绕这个path来布局,值得一提的是,这个path可以不是规则的,可以画成心形,蝴蝶型,只要你画得出,任何path都可以。
(2)用CoreText,给要放图片的位置先用一个文字或者其他特殊字符占位,我们
先来看看官方文档怎么说:

019-谈谈iOS 图文混排_第1张图片
CoreText文字布局官方解释.png

这几段话主要的意思是:
(1)在运行时,Core Text 中,通过attributedString创建出一个CTFramesetter,一个CTFramesetter会生成一个或者多个CTFrame,每个CTFrame代表这个一个段落。
(2)CTFramesetter会调用CTTypesetter,用来将我们设置好的attribute设置到字符上面,比如对齐,缩进,行间距等等。
(3)每个CTFrame对象中包含了这个段落的所有“行”对象,每个行对象就代表一行的字符,就是上图中的CTLine对象。
(4)每个CTLine包含一个或多个CTRun,每个CTRun代表具有相同attributes(属性)和direction(方向)的字符。
CTRun中有对应的属性信息和frame信息,通过下面两个API可以获取到:

NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);
// 获取run的宽度,并且将上行高度和下行高度保存在对应的&runAscent及 &runDescent中
CGFloat runWidth  = CTRunGetTypographicBounds(run, CFRangeMake(0,0), &runAscent, &runDescent, NULL);

另外值得一提的是,可以通过上行高度和下行高度,得到每行的精确高度,可以用来计算label的真实的高度,我能吐槽

boundingRectWithSize:options:context:

有计算误差么?感兴趣的同学可以试试,数量稍大的字符串用它计算试试就知道了。

知道了原理,我们就可以这样想:

先用某个特殊字符(串)占位,获取其CTRun的位置和尺寸,然后在对应的位置和尺寸上面放上图片,不就是在文字中插入图片了么?最简单的图文混排就出来了嘛。

下面先上核心代码:(回头再传demo到git上,太忙了)
我自己定义的中间对象StringModel,用来保存一些中间值,统一传值用。


019-谈谈iOS 图文混排_第2张图片
StringModel.png

第一步创建CTFrameSetter:

+(void)createCTFrameSInStringModel:(StringModel *)stringModel
{
    
    stringModel.ctFrameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)stringModel.attributedString);
    
    CGSize size = CGSizeMake(stringModel.width, MAXFLOAT);
    //获取最佳高度
    CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(stringModel.ctFrameSetter, CFRangeMake(0, stringModel.attributedString.length), nil, size, nil);
    stringModel.aspactHeight = coreTextSize.height;
    NSLog(@"------最佳高度------》%f",stringModel.aspactHeight);
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0, 0, stringModel.width, stringModel.aspactHeight));
    
    stringModel.ctFrame = CTFramesetterCreateFrame(stringModel.ctFrameSetter, CFRangeMake(0, 0), path, NULL);
    CFRelease(path);
    CFRelease(stringModel.ctFrameSetter);
}

第二步找出标志位字符串的位置,并记录下来:

/**
 *  取得所有标记的位置和所占尺寸
 *
 */
+ (void)calculateFramesOfCTRunsInStringModel:(StringModel *)stringModel
{
    CTFrameRef ctFrame = stringModel.ctFrame;
    //获取所有的lines
    CFArrayRef lines = CTFrameGetLines(ctFrame);
    CFIndex count = CFArrayGetCount(lines);
    CGPoint lineOrigins[count];
    CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), lineOrigins);
    CGFloat heightCount = 0;
    stringModel.totalNumberOfLines = (NSInteger)count;
    for (int i = 0; i < count; i++) {
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        CGFloat lineAscent;
        CGFloat lineDescent;
        CGFloat lineLeading;
        CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
        //获取每个line中所有的runs
        CFArrayRef runs = CTLineGetGlyphRuns(line);
        CGFloat runHeight = 0;
        for (int j = 0; j < CFArrayGetCount(runs); j++) {//找到并计算出所有标志位run的frame
            CGFloat runAscent;
            CGFloat runDescent;
            CGPoint lineOrigin = lineOrigins[i];
            CTRunRef run = CFArrayGetValueAtIndex(runs, j);
            NSDictionary *attributes = (NSDictionary *)CTRunGetAttributes(run);
            CGRect runRect;
            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);
            runHeight = runRect.size.height;
            stringModel.perlineHeight = runHeight;
            //runAscent + runDescent 就是每行行高
            
            NSString *flagName = [attributes objectForKey:stringModel.flag];
            if (flagName) {//如果有值,代表就是标志位run
                
                CGRect rect;
                id objW = stringModel.flagAtrribtuesDic[flagWidthKey];
                id objH = stringModel.flagAtrribtuesDic[flagHeightKey];
                if ([objH isKindOfClass:[NSNumber class]] && [objW isKindOfClass:[NSNumber class]]) {
                    NSNumber *width = (NSNumber *)objW;
                    NSNumber *height = (NSNumber *)objH;
                    rect.size = CGSizeMake(width.floatValue, height.floatValue);
                }
                else{
                    rect.size = CGSizeMake(kDefaultWidth, kDefaultHeight);
                }
                    rect.origin.x = runRect.origin.x + lineOrigin.x;
                    rect.origin.y = stringModel.aspactHeight - lineOrigin.y - rect.size.height; //坐标变换
                    NSValue *value = [NSValue valueWithCGRect:rect];
                    [stringModel.flagsFrameArray addObject:value];
            }
            
            CFRelease(run);
            CFRelease((__bridge CFDictionaryRef)attributes);
        }
        heightCount += runHeight;//不含行间距
    }
    
    stringModel.heightCount = heightCount;
    
}

// 待续。。。

你可能感兴趣的:(019-谈谈iOS 图文混排)