美文网首页JC专题
CoreText富文本处理

CoreText富文本处理

作者: coderZ | 来源:发表于2016-05-14 11:00 被阅读505次

    解析纯文本字符串

    源码github地址:https://github.com/zsmzhu/MinRichText.git
    通过正则表达式解析纯文本字符串,将其分成三种类型:@xxx、网址、表情

    @正则:@"@[-_a-zA-Z0-9\u4E00-\u9FA9]+"

    网址正则: @"((http[s]{0,1}|ftp)://[a-zA-Z0-9\.\-]+\.([a-zA-Z]{2,4})(:\d+)?(/[a-zA-Z0-9\.\-!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\.\-]+\.([a-zA-Z]{2,4})(:\d+)?(/[a-zA-Z0-9\.\-!@#$%^&+?:_/=<>])?)"

    表情正则:@"\[[^ \[\]]+?\]"

    将纯文本的NSString类型的Content转化为NSMutableAttributedString的Result返回
    首先处理表情,因为表情会使用空白占位符替换,导致NSString的Range改变,在完成表情解析后才能进行下一步的@、网址解析

    表情解析

    使用iOS自带正则解析,得到匹配正则的字符串数组
    遍历数组生成空白占位符,
    注意:在此过程中将非表情字段也转换为NSAttributedString添加到需要返回的Result中
    1.定义CTRunDelegateCallbacks
    由此设置好表情描绘时候的大小Size,将CTRunDelegateCallbacks添加到Result的属性字典中,描绘的时候取出来使用
    2.添加自定义属性字典
    将表情图片的名字、Range位置、富文本类型type枚举值添加到属性字典中

    链接解析、@解析

    链接解析和@解析一样
    遍历匹配数组并且添加自定义处理的属性字典

    CoreText的绘制

    重写drawRect:方法
    1.由解析完成的attributedString获取描绘区域
    2.由于CoreText坐标系原点为左下角,进行描绘前需要翻转坐标系(上下翻转)

        CGContextRef context = UIGraphicsGetCurrentContext();
        CGAffineTransform flipVertial = CGAffineTransformMake(1, 0, 0, -1, 0, rect.size.height);
        CGContextConcatCTM(context, flipVertial);
    

    3.使用最灵活的CTRunDraw进行描绘
    CoreText中三大层级关系为CTFrame、CTLine、CTRun
    CTRun为相同属性的字符,CTLine由一行CTRun组成、CTFrame则是所有需要描绘的CTLine组成
    首先遍历每一行CTLine
    再遍历CTLine中的CTRun、获取CTRun属性
    NSDictionary *attDic = (__bridge NSDictionary *)CTRunGetAttributes(run);

    根据之前存储的富文本类型type枚举属性分别进行绘制
    对于@和链接需要点击的字符首先绘制点击高亮背景、然后再绘制文字,不然文字会被背景覆盖
    链接绘制下划线代码

     // 这里需要绘制下划线,记住CTRun是不会自动绘制下滑线的
     // 即使你设置了这个属性也不行
     // CTRun.h中已经做出了相应的说明
     // 所以这里的下滑线我们需要自己手动绘制
     CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
     CGContextSetLineWidth(context, 0.5);
     CGContextMoveToPoint(context, runBounds.origin.x, runBounds.origin.y);
     CGContextAddLineToPoint(context, runBounds.origin.x + runBounds.size.width, runBounds.origin.y);
     CGContextStrokePath(context);
    

    计算绘制bounds,文字部分height由行高决定

    // 获取CTLine坐标点
    CGPoint originArray[lineCount];
    CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), originArray);
    // 获取CTLine的上行高度、下行高度
    CGFloat lineAscent, lineDescent;
    CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, NULL);
    // 计算CTRun的bounds
    CGFloat width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL);
    CGFloat height = lineAscent + lineDescent;
    CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
    CGFloat x = originArray[i].x + xOffset;
    CGFloat y = originArray[i].y - lineDescent;
    CGRect runBounds = CGRectMake(x, y, width, height);
    

    描绘表情根据之前设置的delegate重新计算bounds,

    // 重新表情图片大小计算
    CGFloat ascent, descent, leading, emojiHeight, emojiWidth;
    emojiWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading);
    emojiHeight = ascent + descent;
    runBounds = CGRectMake(x, y, emojiWidth, emojiHeight);
    
    // 获取表情图片
    NSString *emojiName = attDic[kEmojiAttributeName];
    UIImage *emoji = [UIImage imageNamed:emojiName];
    // 绘制表情
    CGContextDrawImage(context, runBounds, emoji.CGImage);
    

    点击处理

    有几个属性进行相关记录处理

    @property (nonatomic, assign) UITouchPhase touchPhase;/*!< 点击状态 */
    @property (nonatomic, assign) CGPoint beginPoint;/*!< 开始点击的坐标点 */
    @property (nonatomic, assign) CGPoint endPoint;/*!< 结束点击的坐标点 */
    @property (nonatomic, assign) CFIndex beginIndex;/*!< 开始点击的Range位置 */
    @property (nonatomic, assign) CFIndex endIndex;/*!< 结束点击的Range位置 */
    

    记录点击位置坐标点、进行翻转转换为CoreText坐标系的点。
    遍历找到相对于的CTRun,获取点击事件相关信息、处理点击回调、重新绘制文字。显示高亮背景

    bug待解决

    @中文文字,没办法WordWrapping

    相关文章

      网友评论

        本文标题:CoreText富文本处理

        本文链接:https://www.haomeiwen.com/subject/adbmrttx.html