图文混排
CoreText实际上并没有相应API直接将一个图片转换为CTRun并进行绘制,它所能做的只是为图片预留相应的空白区域,而真正的绘制则是交由CoreGraphics完成。(像OSX就方便很多,直接将图片打包进NSTextAttachment即可,根本无须操心绘制的事情,所以基于这个想法,M80AttributedLabel的接口和实现也是使用了attachment这么个概念,图片或者UIView都是被当作文字段中的attachment。)
在CoreText中提供了CTRunDelegate这么个Core Foundation类,顾名思义它可以对CTRun进行拓展:AttributedString某个段设置kCTRunDelegateAttributeName属性之后,CoreText使用它生成CTRun是通过当前Delegate的回调来获取自己的ascent,descent和width,而不是根据字体信息。这样就给我们留下了可操作的空间:用一个空白字符作为图片的占位符,设好Delegate,占好位置,然后用CoreGraphics进行图片的绘制。以下就是整个图文混排代码描述的过程:
// 自定检查图片,并处理图片相关信息 -> 存放图片数据模型的数组
self.imageArr = [[self.attributedString setImageWithImageSize:self.imageSize font:self.font] mutableCopy];
/**
* 绘制图片
*/
- (void)drawImages
{
// 如果_frameRef不存在,直接退出
if (!self.frameRef) return;
// 移除以前的图片视图
[self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj removeFromSuperview];
}];
// 1.获取需要展示的行数
// 1.1获取lineRef的数组
CFArrayRef lines = CTFrameGetLines(self.frameRef);
// 1.2获取lineRef的个数
CFIndex lineCount = CFArrayGetCount(lines);
// 1.3计算需要展示的行树
NSUInteger numberOfLines = self.numberOfLines != 0 ? MIN(lineCount, self.numberOfLines) : lineCount;
// 2.获取每一行的起始位置数组
CGPoint lineOrigins[numberOfLines];
CTFrameGetLineOrigins(self.frameRef, CFRangeMake(0, numberOfLines), lineOrigins);
// 3.循环遍历每一组中是否包含link
for (CFIndex idx = 0; idx < numberOfLines; idx ++) {
// 3.1寻找图片占位符的准备工作
// 3.1.1获取idx对应行的lineRef
CTLineRef lineRef = CFArrayGetValueAtIndex(lines, idx);
// 3.1.2获取当前lineRef中的runRef数组
CFArrayRef runs = CTLineGetGlyphRuns(lineRef);
// 3.1.3获取当前lineRef中的runRef的个数
CFIndex runCount = CFArrayGetCount(runs);
// 3.1.4获取每一行对应的位置
CGPoint lineOrigin = lineOrigins[idx];
// 3.2遍历lineRef中的runRef,查找图片占位符
for (CFIndex idx = 0; idx < runCount; idx ++) {
// 3.2.1获取lineRef中对应的RunRef
CTRunRef runRef = CFArrayGetValueAtIndex(runs, idx);
// 3.2.2获取对应runRef的属性字典
NSDictionary *runAttributes = (NSDictionary *)CTRunGetAttributes(runRef);
// 3.2.3获取对应runRef的CTRunDelegateRef
CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(id)kCTRunDelegateAttributeName];
// 3.2.3如果不存在,直接退出本次遍历
// ->证明不是图片,因为我们只给图片设置了CTRunDelegateRef
if (nil == delegate) continue;
// ->证明图片在runRef里
// 4.开始绘制图片
// 4.1获取图片的数据模型
ZYAttributedImage *imageData = (ZYAttributedImage *)CTRunDelegateGetRefCon(delegate);
// 4.2获取需要展示图片的frame
CGRect imageFrame = CTRunGetTypographicBoundsForImageRect(runRef, lineRef, lineOrigin, imageData);
// 4.3添加图片
if (imageData.imageType == SXTImageGIFTppe) {
// 初始化imageView
UIImageView *imageView = [UIImageView imageViewWithGIFName:imageData.imageName frame:imageFrame];
// 调整imageView的Y坐标
[imageView setY:self.height - imageView.height - imageView.y];
[self addSubview:imageView];
}else{
// 添加图形上下文
CGContextRef context = UIGraphicsGetCurrentContext();
UIImage *image = [UIImage imageNamed:imageData.imageName];
// 绘制图片
CGContextDrawImage(context, imageFrame, image.CGImage);
}
}
}
}
网友评论