富文本绘制步骤
- 首先需要一个 StringA;
- 把 StringA 转成 attributeString,并添加相关样式;
- 生成 CTFramessetter,得到 CTFrame;
- 绘制 CTFrameDraw。
绘制完成后,因为绘制只是显示,其他的需要额外操作。
如响应相关点击事件原理:
CTFrame 包含了多个 CTLine,并且可以得到每个 line 的起始位置和大小,计算出响应的区域范围,然后根据点击坐标来判断是否在响应区。
又如图片显示原理:
先用空白占位符将位置保留出来,然后再添加图片和其他。
富文本绘制需要引入框架 #import
自定义 label,在自定义 label 中按照绘制出来的文字获取信息(位置)。
- (void)drawRect:(CGRect)rect {
// 富文本字符串
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:self.text attributes:nil];
// 添加属性
[attrStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16] range:NSMakeRange(0, self.text.length)];
NSRange sepRange = NSMakeRange(40, 5);
[attrStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:sepRange];
// 生成 CTFrame
CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
CGPathRef pathRef = CGPathCreateWithRect(CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), &CGAffineTransformIdentity);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(0, 0), pathRef, nil);
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// 调整坐标
CGContextSetTextMatrix(contextRef, CGAffineTransformIdentity);
CGContextTranslateCTM(contextRef, 0, self.frame.size.height);
CGContextScaleCTM(contextRef, 1, -1);
// 绘制
CTFrameDraw(frameRef, contextRef);
// 获取信息
NSArray *lineArr = (__bridge NSArray *)CTFrameGetLines(frameRef);
CGPoint pointArr[lineArr.count];
memset(pointArr, 0, sizeof(pointArr));
CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), pointArr); // 由于坐标系关系, 不直接通过这种方式拿行(CTLine)的起始位置
double heightAddup = 0; // Y
// CTLine 信息
for (int i = 0 ; i < lineArr.count; i++) {
CTLineRef lineRef = (__bridge CTLineRef)lineArr[i];
NSArray *runArr = (__bridge NSArray *)CTLineGetGlyphRuns(lineRef);
CGFloat ascent = 0; // 上行高度
CGFloat descent = 0; // 下行高度
CGFloat lineGap = 0; // 行间距
CTLineGetTypographicBounds(lineRef, &ascent, &descent, &lineGap);
double startX = 0;
// CTRun 信息
// 字的高度
double runHeight = ascent + descent + lineGap;
for (int j = 0; j < runArr.count; j++) {
CTRunRef runRef = (__bridge CTRunRef)runArr[j];
CFRange runRange = CTRunGetStringRange(runRef);
double runWidth = CTRunGetTypographicBounds(runRef, CFRangeMake(0, 0), 0, 0, 0);
if (runRange.location == sepRange.location && runRange.length == sepRange.length) {
NSLog(@"找到位置"); // 计算需要的位置和 rect
NSLog(@"x:%f...y:%f...w:%f...h:%f", startX, heightAddup, runWidth, runHeight);
sepRect = CGRectMake(startX, heightAddup, runWidth, runHeight);
}
startX += runWidth;
}
// 字的高度叠加
heightAddup += runHeight;
}
}
找到位置之后就可以添加点击事件了。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
if (CGRectContainsPoint(sepRect, point)) {
NSLog(@"点击了红色的文字");
}
}
不要忘了将 label 的userInteractionEnabled
属性设置为 YES。
或者在找到的位置上添加 button,然后给 button 设置事件。
含图片的富文本
#define YYCoreTextImageWidthPro @"YYCoreTextImageWidthPro"
#define YYCoreTextImageHeightPro @"YYCoreTextImageHeightPro"
static CGFloat ctRunDelegateGetWidthCallback(void *refCon) {
NSDictionary *infoDict = (__bridge NSDictionary *)(refCon);
if ([infoDict isKindOfClass:[NSDictionary class]]) {
return [infoDict[YYCoreTextImageWidthPro] floatValue];
}
return 0;
}
static CGFloat ctRunDelegateGetAscentCallback(void *refCon) {
NSDictionary *infoDict = (__bridge NSDictionary *)(refCon);
if ([infoDict isKindOfClass:[NSDictionary class]]) {
return [infoDict[YYCoreTextImageHeightPro] floatValue];
}
return 0;
}
static CGFloat ctRunDelegateGetDescentCallback(void *refCon) {
return 0;
}
static NSMutableDictionary *argDic = nil;
@implementation YYImageLabel
{
NSInteger imageSpaceIndex;
CGRect sepRect;
UIImageView *_imageView;
}
- (void)drawRect:(CGRect)rect {
// 富文本字符串
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:self.text attributes:nil];
// 添加属性
[attrStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16] range:NSMakeRange(0, self.text.length)];
// 图片占位符
imageSpaceIndex = self.text.length;
[attrStr appendAttributedString:[self sepImageSpaceWidth:50 height:30]];
NSMutableAttributedString *textAttrStr = [[NSMutableAttributedString alloc] initWithString:@"bhiuhsdfiohsifwfd" attributes:nil];
[attrStr appendAttributedString:textAttrStr];
// 生成 CTFrame
CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
CGPathRef pathRef = CGPathCreateWithRect(CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), &CGAffineTransformIdentity);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(0, 0), pathRef, nil);
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// 调整坐标
CGContextSetTextMatrix(contextRef, CGAffineTransformIdentity);
CGContextTranslateCTM(contextRef, 0, self.frame.size.height);
CGContextScaleCTM(contextRef, 1, -1);
// 绘制
CTFrameDraw(frameRef, contextRef);
// 获取信息
NSArray *lineArr = (__bridge NSArray *)CTFrameGetLines(frameRef);
CGPoint pointArr[lineArr.count];
memset(pointArr, 0, sizeof(pointArr));
CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), pointArr); // 由于坐标系关系, 不直接通过这种方式拿行(CTLine)的起始位置
double heightAddup = 0; // Y
// CTLine 信息
for (int i = 0 ; i < lineArr.count; i++) {
CTLineRef lineRef = (__bridge CTLineRef)lineArr[i];
NSArray *runArr = (__bridge NSArray *)CTLineGetGlyphRuns(lineRef);
CGFloat ascent = 0; // 上行高度
CGFloat descent = 0; // 下行高度
CGFloat lineGap = 0; // 行间距
CTLineGetTypographicBounds(lineRef, &ascent, &descent, &lineGap);
double startX = 0;
// CTRun 信息
// 字的高度
double runHeight = ascent + descent + lineGap;
for (int j = 0; j < runArr.count; j++) {
CTRunRef runRef = (__bridge CTRunRef)runArr[j];
CFRange runRange = CTRunGetStringRange(runRef);
double runWidth = CTRunGetTypographicBounds(runRef, CFRangeMake(0, 0), 0, 0, 0);
if (imageSpaceIndex == runRange.location && imageSpaceIndex < runRange.location + runRange.length) {
NSLog(@"找到位置"); // 计算需要的位置和 rect
NSLog(@"x:%f...y:%f...w:%f...h:%f", startX, heightAddup, runWidth, runHeight);
sepRect = CGRectMake(startX, heightAddup, runWidth, runHeight);
}
startX += runWidth;
}
// 字的高度叠加
heightAddup += runHeight;
}
[self setNeedsLayout];
}
- (void)layoutSubviews {
if (sepRect.size.width > 0) {
if (!_imageView) {
_imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"1.png"]];
[self addSubview:_imageView];
}
[_imageView setFrame:sepRect];
}
}
- (NSMutableAttributedString *)sepImageSpaceWidth:(float)width height:(float)height {
CTRunDelegateCallbacks callbacks;
memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));
callbacks.getWidth = ctRunDelegateGetWidthCallback;
callbacks.getAscent = ctRunDelegateGetAscentCallback;
callbacks.getDescent = ctRunDelegateGetDescentCallback; // 0
callbacks.version = kCTRunDelegateVersion1;
// 创建占位符
NSMutableAttributedString *spaceAttrStr = [[NSMutableAttributedString alloc] initWithString:@" "];
// 参数动态化
argDic = [NSMutableDictionary dictionary];
[argDic setValue:@(width) forKey:YYCoreTextImageWidthPro];
[argDic setValue:@(height) forKey:YYCoreTextImageHeightPro];
CTRunDelegateRef runDelegateRef = CTRunDelegateCreate(&callbacks, (__bridge void *)argDic);
// 配置占位的属性
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)spaceAttrStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, runDelegateRef);
return spaceAttrStr;
}