做图文混排离不开CoreText
,大体思路是 把图片用空的占位字符\uFFFC
替换,保存图片的宽高,然后设置对应AttributeString
的CTRunDelegateRef
,CTFrameDraw
或者CTLineDraw
的时候就会有留出图片的空间,然后通过CGContextDrawImage
或者[UIView addSubView]
等方式 把图片显示出来,代码如下
- (NSDictionary *)p_addAttri:(int)i{
CGFloat pSpacing = 0;
pSpacing = roundf(0);
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
CTLineBreakMode lineBreak = kCTLineBreakByCharWrapping;
UIFont *f = [UIFont systemFontOfSize:20];
CFStringRef fontName = (__bridge CFStringRef)f.fontName;
CGFloat fontSize = f.pointSize;
CTFontRef ctfont = CTFontCreateWithName(fontName, fontSize, NULL);
[attributes setObject:(__bridge id)ctfont forKey:(id)kCTFontAttributeName];
CTParagraphStyleSetting setting[] = {
{ kCTParagraphStyleSpecifierParagraphSpacing, sizeof(pSpacing), &pSpacing },
{ kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreak }
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(setting, sizeof(setting) / sizeof(CTParagraphStyleSetting));
[attributes setObject:(__bridge id)paragraphStyle forKey:NSParagraphStyleAttributeName];
// 注意这里要释放paragraphStyle 因为 addAttribute方法会Retain 一下
GWWTextAttachmentCallbackModel *model = [GWWTextAttachmentCallbackModel new];
if (i == 1) {
model.size = CGSizeMake(360, 20);
}else{
model.size = CGSizeMake(360, 100);
}
model.originalString = @"\uFFFC";
CTRunDelegateCallbacks callbacks = model.callbacks;
CTRunDelegateRef runDelegate = CTRunDelegateCreate(&callbacks, (__bridge_retained void *)model);
[attributes setObject:(__bridge id _Nonnull)(runDelegate) forKey:(NSString *)kCTRunDelegateAttributeName];
CFRelease(paragraphStyle);
CFRelease(runDelegate);
return attributes;
}
NSMutableAttributedString *atrriStr = [[NSMutableAttributedString alloc] initWithString:@"\uFFFC\uFFFC\uFFFC"];
[atrriStr addAttributes:[self p_addAttri:0] range:NSMakeRange(0, 1)];
[atrriStr addAttributes:[self p_addAttri:1] range:NSMakeRange(1, 1)];
[atrriStr addAttributes:[self p_addAttri:2] range:NSMakeRange(2, 1)];
static CGFloat RunDelegateGetAscentCallback(void *refCon){
GWWTextAttachmentCallbackModel *object = (__bridge GWWTextAttachmentCallbackModel *)refCon;
return object.size.height;
}
static CGFloat RunDelegateGetDescentCallback(void *refCon){
return 0.0f;
}
static CGFloat RunDelegateGetWidthCallback(void *refCon){
GWWTextAttachmentCallbackModel *object = (__bridge GWWTextAttachmentCallbackModel *)refCon;
return object.size.width;
}
上面的代码中GWWTextAttachmentCallbackModel
是一个简单的模型类,保存图片宽高,方便存取。
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)atrriStr);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL,rect);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, atrriStr.length), path,nil);
CFArrayRef lines = CTFrameGetLines(frame);
CFIndex count = CFArrayGetCount(lines);
CGPoint origin[count];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0),origin);
// first line
CGFloat ascent,descent,leading,width = 0;
CTLineRef line1 = CFArrayGetValueAtIndex(lines,0);
width = CTLineGetTypographicBounds(line1, &ascent, &descent, &leading);
CGPoint line1Pos = origin[0];
CGRect line1Rect = CGRectMake(line1Pos.x, line1Pos.y - descent, width, ascent + descent);
CGRect baseLine1 = CGRectMake(line1Pos.x, line1Pos.y, width,0.1);
// second line
CTLineRef line2 = CFArrayGetValueAtIndex(lines,1);
width = CTLineGetTypographicBounds(line2, &ascent, &descent, &leading);
CGPoint line2Pos = origin[1];
CGRect line2Rect = CGRectMake(line2Pos.x, line2Pos.y - descent, width, ascent + descent);
CGRect baseLine2 = CGRectMake(line2Pos.x, line2Pos.y, width,0.1);
// last line
CTLineRef lastLine = CFArrayGetValueAtIndex(lines, count - 1);
width = CTLineGetTypographicBounds(lastLine, &ascent, &descent, &leading);
CGPoint lastLinePos = origin[count - 1];
CGRect lastLineRect = CGRectMake(lastLinePos.x, lastLinePos.y - descent, width, ascent + descent);
CGRect baseLine = CGRectMake(lastLinePos.x, lastLinePos.y, width,0.1);
NSLog(@"lastlast rect= %@",NSStringFromCGRect(lastLineRect));
CFRange lastLinerange = CTLineGetStringRange(lastLine);
NSRange nsLastLineRange = NSMakeRange(lastLinerange.location, lastLinerange.length);
上面的代码是计算每行显示的rect
,baseline 的rect。
UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"img.jpeg" ofType:nil]];
CGContextDrawImage(ctx, line1Rect, img.CGImage);
CGContextDrawImage(ctx, lastLineRect, img.CGImage);
绘制图片的代码。
image.png
如图,红色矩形区域表示每一行的区域,就是图片占据的区域,蓝色的线表示的是每行的baseLine,CoreText在进行文本绘制的时候以baseLine为参看绘制文字的,每个蓝色线到对应矩形顶部的高度是ascent,到底部的是descent。
如上图可以发现,如果我们图片宽高是 300 * 50 的话,但是实际区域是300 * 55 高度多出来了5,其实这个5的高度就是descent。
问题就是如何让5 变成0?
其实 descent 和当前attribute string 中font 属性相关,font size 约到,descent 就越大,我们把font size = 1,descent 就会很小, 可以忽略了。
UIFont *f = [UIFont systemFontOfSize:1];
image.png
看一下下面这个问题
UIFont *f = [UIFont systemFontOfSize:20];
if (i == 1) {
model.size = CGSizeMake(360, 5);
}else{
model.size = CGSizeMake(360, 100);
}
字号 设为20 , 其中第二行的高度设置为5后 效果如下图:
image.png
可以看到第一行和第二行中间竟然不是连续的而是空出了一部分区域,为啥呢?
跟踪一下具体代码,发现 第一行的baseLine 距离第二行的baseLine 间距大概24,每行的descent 为4 , 24-4 = 20 正好是字体的大小。
由此得出,CoreText 在计算的时候主要是参看每行attributestring中font 属性的值,如果font 高度为20 ,CoreText在计算的时候就至少会留出20的高度。
当把字体设为1,其他什么都不改 如图
image.png
。
网友评论