做了一个部分文字加圆角背景色的UILabel,用到了CoreText,稍微研究了一下,主要代码分享一下,供大家参考,有更好的解决方案请一定要告诉我。
先来张框架图便于后面的理解
一个CTFrame是由一行一行的CLine组成,每个CTLine又会包含若干个CTRun,CTRun包含说要绘制的字形的所有信息,得到CTRun之后我们就可以检查它的属性,如果是我们要添加圆角背景的内容,我们可以拿到它的frame,用UIBezierPath画出一个圆角矩形,填充颜色就可以了。
首先我们要对需要添加圆角背景的那部分内容标记起来,比如添加一个自定义的属性。
[string addAttribute:@"RoundBackgroundText" value:[UIColor yellowColor] range:NSMakeRange(range.location, [self.highlightText length])];
得到当前用于绘制画布的上下文
然后翻转当前的坐标系
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);//变换坐标系原点
CGContextTranslateCTM(context, 0, self.bounds.size.height);//横移变换,在这里正值是向右和向上
CGContextScaleCTM(context, 1.0, -1.0);//缩放变换
创建绘制区域
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, self.bounds.size.width/2, self.bounds.size.height));
先根据AttributedString生成CTFramesetterRef
CTFramesetterRef是生成CTFrameRef的工厂
生成CTFrameRef
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithAttributedString:self.text];
CTFramesetterRef frameSetter = CTFramesetterRef framesetter2 = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)string));
CTFrameRef totalFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
从CTFrame上获取一个CTLine对象的数组,再获取每一个CTLine的坐标点
NSArray* lines = (__bridge NSArray*)CTFrameGetLines(totalFrame);//获取CTLine对象数组
CFIndex lineCount = [lines count];//CTLine数量
CGPoint origins[lineCount];//CTLine坐标点声明
CTFrameGetLineOrigins(totalFrame, CFRangeMake(0, 0), origins);//获取坐标点
前面说过我们需要检查每一个CTRun,所以我们需要用一个for循环获取每个CTLine,然后检查CTLine上的每一个CTRun,检查是否有设置圆角的属性。
for(CFIndex index = 0; index < lineCount; index++)
{
CTLineRef line = CFArrayGetValueAtIndex((CFArrayRef)lines, index);//获取对应CTLine
CFArrayRef glyphRuns = CTLineGetGlyphRuns(line); //从CTLine中获取CTRun的数组
CFIndex glyphCount = CFArrayGetCount(glyphRuns);//CTRun数组的个数
for (CFIndex i = 0; i < glyphCount; ++i) {
CTRunRef run = CFArrayGetValueAtIndex(glyphRuns, i);//获取对应的CTRun
......
......
}
}
这样,我们就拿到了一个CTRun对象,接下来我们要判断是否需要对改段文字添加圆角背景色。
CTRunRef run = CFArrayGetValueAtIndex(glyphRuns, i);
NSDictionary *attributes = (__bridge NSDictionary*)CTRunGetAttributes(run);
if ([attributes objectForKey:@"RoundBackgroundText"]){//判断是否需要添加圆角背景
CGRect runBounds;
...
}
重点来了,就是拿到CTRun的frame,然后根据这个和前面的CTLine坐标点在画布上画一个圆角背景,话不多说,直接上代码
还是上一张图便于理解
CGRect runBounds;
CGFloat ascent, descent;
runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
runBounds.size.height = ascent + descent;//我只画字高,行高不画背景色
runBounds.origin.x = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL) + origins[index].x; //当前CTRun在line中的x轴坐标加上line在CTFrame中的x坐标
runBounds.origin.y = origins[index].y - 2;//y轴坐标,稍微移动一些,上下均衡
CGColorRef highlightColor = (_highlightColor) ? _highlightColor.CGColor : [UIColor yellowColor].CGColor;
[[UIColor colorWithCGColor:highlightColor] setFill]; //填充色
// 创建圆角矩形路径对象
UIBezierPath* path;
[string attributesAtIndex:CTRunGetStringRange(run).location effectiveRange:&_rangePointer];//找到和CTRunGetStringRange(run).location属性一样的range赋值给_rangePointer
if (CTRunGetStringRange(run).location > _rangePointer.location && CTRunGetStringRange(run).location + CTRunGetStringRange(run).length < _rangePointer.location + _rangePointer.length) {
path = [UIBezierPath bezierPathWithRect:runBounds]; // 直角
} else if (CTRunGetStringRange(run).location == _rangePointer.location && CTRunGetStringRange(run).location + CTRunGetStringRange(run).length < _rangePointer.location + _rangePointer.length) {
// 创建圆角矩形路径对象
path = [UIBezierPath bezierPathWithRoundedRect:runBounds byRoundingCorners:UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii:CGSizeMake(5, 5)]; // 圆角半径为5
} else if (CTRunGetStringRange(run).location > _rangePointer.location && CTRunGetStringRange(run).location + CTRunGetStringRange(run).length == _rangePointer.location + _rangePointer.length) {
// 创建圆角矩形路径对象
path = [UIBezierPath bezierPathWithRoundedRect:runBounds byRoundingCorners:UIRectCornerTopRight | UIRectCornerBottomRight cornerRadii:CGSizeMake(5, 5)]; // 圆角半径为5
} else {
path = [UIBezierPath bezierPathWithRoundedRect:runBounds cornerRadius:5]; // 圆角半径为5
}
path.lineCapStyle = kCGLineCapRound;
path.lineJoinStyle = kCGLineCapRound;
[path fill];
上结果