CoreText富文本处理

解析纯文本字符串

源码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富文本处理)