一句話:畫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;
}
// 待續。。。