一个老司机的 Coretext图文混排 非常容易理解
简书:老司机Wicky 可以点击查看.
//注:对上面那位老司机的 demo 代码一行一行的注释 供自己以后学习参考.感谢 老司机Wicky
#import "tuwenview.h"
#import@implementation tuwenview
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
[super drawRect:rect];
//获取到上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//实现翻转
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
//属性字符串
NSMutableAttributedString *attributestr = [[NSMutableAttributedString alloc]initWithString:@"\\n这里在测试图文混排,\\n我是一个富文本"];
//为图片设置CTRunDelegate,delegate决定留给图片的空间大小
CTRunDelegateCallbacks callbacks;
memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));
callbacks.version = kCTRunDelegateVersion1;
callbacks.getAscent = ascentCallBacks;
callbacks.getDescent = descentCallBacks;
callbacks.getWidth = widthCallBacks;
NSDictionary *dicpic = @{@"height":@129,@"width":@400};
//通过以上创建了图片显示大小的代理
CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void * )(dicpic));
//不能直接显示图片而是要在原来的属性字符创中插入一个占位符
unichar placeholderchar = 0xfffc;
NSString *placeholderStr = [NSString stringWithCharacters:&placeholderchar length:1];
//将占位字符串转化为属性字符串
NSMutableAttributedString * placeHolderAttrStr = [[NSMutableAttributedString alloc] initWithString:placeholderStr];
//不太明白这里是什么意思
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderAttrStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);
//这个 delegate 使用完毕 释放内存 已经存储在placeHolderAttrStr中了
CFRelease(delegate);
//将这个属性字符串插入到需要混排的属性字符串中间
[attributestr insertAttributedString:placeHolderAttrStr atIndex:8];
//下面是获取到 ctframe 的套路
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributestr);
//定义一个需要将混排后的显示区域
CGMutablePathRef path = CGPathCreateMutable();
//设置混排的区域为从从当前视图的左上角开始的矩形区域
CGPathAddRect(path, NULL, self.bounds);
//需要排列的属性文本的大小长度 不同长度 排出来的 frame 大小不一样
NSInteger length = attributestr.length;
//通过前面的长度回去 frame (其中最后一个参数的用来定制这个 ctframe)
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, length), path, NULL);
//混排
CTFrameDraw(frame, context);
//拿到需要排列的图片
UIImage *image = [UIImage imageNamed:@"bg.png"];
//从上面混排的frame 计算实际 image 的 frame 这个计算大小的方式自定义
CGRect imgframerect = [self calculateImageRectWithFrame:frame];
CGContextDrawImage(context, imgframerect, image.CGImage);
//手动释放内存
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);
//关闭上下文 忘了怎么写
}
//为了代理获取到这个图片占位符的实际大小使用的几个 C 函数
static CGFloat ascentCallBacks (void * ref){
return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"height"] floatValue];
}
//获取到 descent
static CGFloat descentCallBacks(void * ref)
{
return 0;
}
//获取到 width
static CGFloat widthCallBacks(void * ref)
{
return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"width"] floatValue];
}
//如何通过全部的计算得到图片的 frame 的方法 定制化
- (CGRect)calculateImageRectWithFrame:(CTFrameRef) rect{
//获取到绘制区域的所有 ctlines
NSArray *arraylines = (NSArray *)CTFrameGetLines(rect);
//获取到 lines 的数量
NSInteger count = [arraylines count];
CGPoint points[count];
//构建一个数组每一个 ctrun的起始点 将诶一个 ctlines 的初始点保存在 points 中间
CTFrameGetLineOrigins(rect, CFRangeMake(0, 0), points);
for(int i = 0 ; i < count ;i++){
//循环拿到每一行 lines
CTLineRef line = (__bridge CTLineRef)arraylines[i];
//获取到每一个行中的所有的 ctrun
NSArray *ctruns = (NSArray *)CTLineGetGlyphRuns(line);
//使用 for 循环遍历该行的所有的 ctrun
for(int j = 0; j < ctruns.count; j++){
CTRunRef run = (__bridge CTRunRef)ctruns[j];
NSDictionary *attributes = (NSDictionary *)CTRunGetAttributes(run);
//获取到该 run 的代理
CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[attributes valueForKey:(id)kCTRunDelegateAttributeName];
//如果代理为空 进入到下一个循环
if(delegate == nil){
continue;
}
NSDictionary *dic = CTRunDelegateGetRefCon(delegate);
//如果有代理 但是代理属性不是 前面赋值的字典类型的 继续下一个循环
if(![dic isKindOfClass:[NSDictionary class]]){
continue;
}
//到这里说明是找到自己的代理了 类型为 字典类型的
//将这个ctrun 的起点保存起来
CGPoint point = points[i];
//上
CGFloat ascent;
//下
CGFloat descent;
CGRect boundsRun;
//获取到宽 (这个宽度为紧贴的宽度)
boundsRun.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
//已经有值了 可以计算出高度
boundsRun.size.height = ascent + descent;
//从当前的行中获取到该 run 的偏移量 这个类似于固定值 应该可以改变
CGFloat xoffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
//设置 origh 的x y
boundsRun.origin.x = point.x + xoffset;
boundsRun.origin.y = point.y - descent;
//绘到屏幕 拿到当前的 ctframe的 path
CGPathRef path = CTFrameGetPath(rect);
CGRect colRect = CGPathGetBoundingBox(path);
//校正 rect
CGRect imageBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
//返回img的 rect
return imageBounds;
}
}
return CGRectZero;;
}
@end