一句话:画path,用Textkit;或者Core Text留位置。
iOS开发中要实现图文混排,简单从API层面看,有两种方法:
(1)用TextKit,设置一个path,然后文字会围绕这个path来布局,值得一提的是,这个path可以不是规则的,可以画成心形,蝴蝶型,只要你画得出,任何path都可以。
(2)用CoreText,给要放图片的位置先用一个文字或者其他特殊字符占位,我们
先来看看官方文档怎么说:
这几段话主要的意思是:
(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,用来保存一些中间值,统一传值用。
第一步创建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;
}
// 待续。。。