core text富文本

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;

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容