https://www.jianshu.com/p/6db3289fb05d
TextKit
AttributedString
CoreText实现图文混排其实就是在富文本中插入一个空白的图片占位符的富文本字符串,通过代理设置相关的图片尺寸信息,根据从富文本得到的frame计算图片绘制的frame再绘制图片这么一个过程。
转载自 CoreText总结 - CSDN博客
(3)图文混排
CTFrameRef textFrame // coreText 的 frame
CTLineRef line // coreText 的 line
CTRunRef run // line 中的部分文字
相关方法:
CFArrayRef CTFrameGetLines (CTFrameRef frame ) //获取包含CTLineRef的数组
void CTFrameGetLineOrigins(
CTFrameRef frame,
CFRange range,
CGPoint origins[] ) //获取所有CTLineRef的原点
CFRange CTLineGetStringRange (CTLineRef line ) //获取line中文字在整段文字中的Range
CFArrayRef CTLineGetGlyphRuns (CTLineRef line ) //获取line中包含所有run的数组
CFRange CTRunGetStringRange (CTRunRef run ) //获取run在整段文字中的Range
CFIndex CTLineGetStringIndexForPosition(
CTLineRef line,
CGPoint position ) //获取点击处position文字在整段文字中的index
CGFloat CTLineGetOffsetForStringIndex(
CTLineRef line,
CFIndex charIndex,
CGFloat* secondaryOffset ) //获取整段文字中charIndex位置的字符相对line的原点的x值
主要步骤:
1)计算并存储文字中保含的所有表情文字及其Range
2)替换表情文字为指定宽度的NSAttributedString
CTRunDelegateCallbacks callbacks;
callbacks.version = kCTRunDelegateVersion1;
callbacks.getAscent = ascentCallback;
callbacks.getDescent = descentCallback;
callbacks.getWidth = widthCallback;
callbacks.dealloc = deallocCallback;
CTRunDelegateRef runDelegate = CTRunDelegateCreate(&callbacks, NULL);
NSDictionary *attrDictionaryDelegate = [NSDictionary dictionaryWithObjectsAndKeys:
(id)runDelegate, (NSString*)kCTRunDelegateAttributeName,
[UIColor clearColor].CGColor,(NSString*)kCTForegroundColorAttributeName,
nil];
NSAttributedString *faceAttributedString = [[NSAttributedString alloc] initWithString:@"*" attributes:attrDictionaryDelegate];
[weiBoText replaceCharactersInRange:faceRange withAttributedString:faceAttributedString];
[faceAttributedString release];
3) 根据保存的表情文字的Range计算表情图片的Frame
textFrame 通过CTFrameGetLines 获取所有line的数组 lineArray
遍历lineArray中的line通过CTLineGetGlyphRuns获取line中包含run的数组 runArray
遍历runArray中的run 通过CTRunGetStringRange获取run的Range
判断表情文字的location是否在run的Range
如果在 通过CTLineGetOffsetForStringIndex获取x的值 y的值为line原点的值
4)Draw表情图片到计算获取到的Frame
(3)点击文字触发事件
主要步骤:
1) 根据touch事件获取点point
2) textFrame 通过CTFrameGetLineOrigins获取所有line的原点
3) 比较point和line原点的y值获取点击处于哪个line
4) line、point 通过CTLineGetStringIndexForPosition获取到点击字符在整段文字中的 index
5) NSAttributedString 通过index 用方法-(NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range 可以获取到点击到的NSAttributedString中存储的NSDictionary
6) 通过NSDictionary中存储的信息判断点击的哪种文字类型分别处理
二、CoreText与UIWebView在排版方面的优劣比较
UIWebView也常用于处理复杂的排版,对应排版他们之间的优劣如下(摘自 《iOS开发进阶》—— 唐巧):
CoreText占用的内容更少,渲染速度更快。UIWebView占用的内存多,渲染速度慢。
CoreText在渲染界面的前就可以精确地获得显示内容的高度(只要有了CTFrame即可),而WebView只有渲染出内容后,才能获得内容的高度(而且还需要用JavaScript代码来获取)。
CoreText的CTFrame可以在后台线程渲染,UIWebView的内容只能在主线程(UI线程)渲染。
基于CoreText可以做更好的原生交互效果,交互效果可以更加细腻。而UIWebView的交互效果都是用JavaScript来实现的,在交互效果上会有一些卡顿的情况存在。例如,在UIWebView下,一个简单的按钮按下的操作,都无法做出原生按钮的即时和细腻的按下效果。
CoreText排版的劣势:
CoreText渲染出来的内容不能像UIWebView那样方便地支持内容的复制。
基于CoreText来排版需要自己处理很多复制的逻辑,例如需要自己处理图片与文字混排相关的逻辑,也需要自己实现连接点击操作的支持。
- (void)drawRect:(CGRect)rect{
[superdrawRect:rect];
//获取当前绘制上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//设置字形的变换矩阵为不做图形变换
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
//平移方法,将画布向上平移一个屏幕高
CGContextTranslateCTM(context, 0, self.bounds.size.height);
//缩放方法,x轴缩放系数为1,则不变,y轴缩放系数为-1,则相当于以x轴为轴旋转180度
CGContextScaleCTM(context,1.0, -1.0);
NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"\n这里在测试图文混排,\n我是一个富文本"];
/*
事实上,图文混排就是在要插入图片的位置插入一个富文本类型的占位符。通过CTRUNDelegate设置图片
*/
CTRunDelegateCallbacks callBacks;
//memset将已开辟内存空间 callbacks 的首 n 个字节的值设为值 0, 相当于对CTRunDelegateCallbacks内存空间初始化
memset(&callBacks,0,sizeof(CTRunDelegateCallbacks));
callBacks.version = kCTRunDelegateVersion1;
//基线为过原点的x轴,ascent即为CTRun顶线距基线的距离,descent即为底线距基线的距离。
callBacks.getAscent = ascentCallBacks;
callBacks.getDescent = descentCallBacks;
callBacks.getWidth = widthCallBacks;
NSDictionary* dicPic =@{@"height":@129,@"width":@400};
CTRunDelegateRefdelegate =CTRunDelegateCreate(& callBacks, (__bridgevoid*)dicPic);
// 设置代理的时候绑定了一个返回图片尺寸的字典。
unicharplaceHolder =0xFFFC;//创建空白字符
NSString* placeHolderStr = [NSStringstringWithCharacters:&placeHolderlength:1];
NSMutableAttributedString * placeHolderAttrStr = [[NSMutableAttributedString alloc] initWithString:placeHolderStr];
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderAttrStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);
CFRelease(delegate);
[attributeStrinsertAttributedString:placeHolderAttrStratIndex:12];
//绘制文字
//一个frame的工厂,负责生成frame
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);
//创建绘制区域
CGMutablePathRef path = CGPathCreateMutable();
//添加绘制尺寸
CGPathAddRect(path, NULL, self.bounds);
NSIntegerlength = attributeStr.length;
CTFrameRefframe =CTFramesetterCreateFrame(frameSetter,CFRangeMake(0, length), path,NULL);
//根据frame绘制文字
CTFrameDraw(frame, context);
//绘制图片
UIImage* image = [UIImageimageNamed:@"timg"];
CGRect imgFrm = [self calculateImageRectWithFrame:frame];
CGContextDrawImage(context,imgFrm, image.CGImage);
CFRelease(frame);
CFRelease(path);
CFRelease(frameSetter);
}
staticCGFloatascentCallBacks(void* ref)
{
return[(NSNumber*)[(__bridgeNSDictionary*)refvalueForKey:@"height"]floatValue];
}
staticCGFloatdescentCallBacks(void* ref)
{
return 0;
}
staticCGFloatwidthCallBacks(void* ref)
{
return[(NSNumber*)[(__bridgeNSDictionary*)refvalueForKey:@"width"]floatValue];
}
-(CGRect)calculateImageRectWithFrame:(CTFrameRef)frame
{
/*就是遍历我们的frame中的所有CTRun,检查他是不是我们绑定图片的那个,如果是,根据该CTRun所在CTLine的origin以及CTRun在CTLine中的横向偏移量计算出CTRun的原点,加上其尺寸即为该CTRun的尺寸。*/
//获取绘制frame中的所有CTLine
NSArray* arrLines = (NSArray*)CTFrameGetLines(frame);
NSIntegercount = [arrLinescount];
CGPointpoints[count];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), points);
for(inti =0; i < count; i ++) {
CTLineRefline = (__bridgeCTLineRef)arrLines[i];
NSArray* arrGlyphRun = (NSArray*)CTLineGetGlyphRuns(line);
for(intj =0; j < arrGlyphRun.count; j ++) {
CTRunRefrun = (__bridgeCTRunRef)arrGlyphRun[j];
NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run); CTRunDelegateRefdelegate = (__bridgeCTRunDelegateRef)[attributesvalueForKey:(id)kCTRunDelegateAttributeName];
if(delegate ==nil) {
continue;
}
NSDictionary* dic =CTRunDelegateGetRefCon(delegate);
if(![dicisKindOfClass:[NSDictionaryclass]]) {
continue;
}
CGPointpoint = points[i];
CGFloatascent;
CGFloatdescent;
CGRectboundsRun;
boundsRun.size.width=CTRunGetTypographicBounds(run,CFRangeMake(0,0), &ascent, &descent,NULL);
boundsRun.size.height= ascent + descent;
//获取x偏移量
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
//point是行起点位置,加上每个字的偏移量得到每个字的x
boundsRun.origin.x= point.x+ xOffset;
boundsRun.origin.y= point.y- descent;//计算原点
CGPathRefpath =CTFrameGetPath(frame);//获取绘制区域
CGRectcolRect =CGPathGetBoundingBox(path);
//获取剪裁区域边框
CGRectimageBounds =CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
returnimageBounds;
}
}
return CGRectZero;
}