019-谈谈iOS 图文混排

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

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

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,用来保存一些中间值,统一传值用。


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

// 待续。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 系列文章: CoreText实现图文混排 CoreText实现图文混排之点击事件 CoreText实现图文混排之文...
    老司机Wicky阅读 40,102评论 221 432
  • 0. 基本知识准备 0.1 字形( Glyph)基本了解 基础原点(Origin)首先是位于基线上处于基线最左侧的...
    破弓阅读 3,135评论 4 24
  • 最近在网上看了一些大牛的文章,自己也试着写了一下,感觉图文混排真的很强大。 废话不多说,开始整 先上效果图跟代码,...
    AllureJM阅读 978评论 0 1
  • blog.csdn.net CoreText实现图文混排 - 博客频道 CoreText实现图文混排 也好久没来写...
    K_Gopher阅读 596评论 0 0
  • 苹果文档 https://developer.apple.com/documentation/coretext C...
    阳明先生_X自主阅读 424评论 0 4