如图一所示,CTFrame作为一个整体的画布,其中有行(CTLine)组成,每行可以分为一个或多个小方块(CTRun),属性一样的字符就分在一个小方块里。
富文本绘制步骤:
1 先需要一个StringA
2 把StringA转成attributeString,并添加相关样式
3 生成CTFramessetter,得到CTFrame
4 绘制CTFrameDraw
绘制完成后,添加其他操作,如响应相关点击事件原理:
CTFrame包含了多个CTLine,并且可以得到每个line的起始位置与大小,计算出你响应的区域范围,然后更具你点击的坐标来判断是否在响应区。
下行高度为负数值
行高= Ascent + |Descent| + Line Gap
一、例子
- 例一,见如图三所示。
@implementation MyTextLbl{
NSString * contentStr;
}
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
contentStr = @"哈哈哈,你是做棒的";
}
return self;
}
- (void)drawRect:(CGRect)rect{
[super drawRect:rect];
NSMutableDictionary * infoDic = [[NSMutableDictionary alloc] init];
[infoDic setObject:[UIFont systemFontOfSize:18] forKey:NSFontAttributeName];
NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:contentStr attributes:infoDic];
[attributeStr addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(0, 3)];
//2 绘制 CFRangeMake(0, 0) 不设置区域
CTFramesetterRef setterRef = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathAddRect(pathRef, NULL, CGRectMake(0, -0, self.frame.size.width, self.frame.size.height));
CTFrameRef frameRef = CTFramesetterCreateFrame(setterRef, CFRangeMake(0, 0), pathRef, NULL);
CGContextRef context = UIGraphicsGetCurrentContext();
//调整坐标
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
//沿x轴移动了0个单位,沿y轴移动了self.bounds.size.height个单位
CGContextTranslateCTM(context, 0, self.bounds.size.height);
//旋转Y轴坐标
CGContextScaleCTM(context, 1, -1.0);
CTFrameDraw(frameRef, context);
NSArray * lineRefAry = (__bridge NSArray *)CTFrameGetLines(frameRef);
NSLog(@"%@",lineRefAry);
CFRelease(frameRef);
CFRelease(pathRef);
CFRelease(setterRef);
}
打印结果:
po lineRefAry
<__NSArrayM 0x604000053c50>(
{run count = 2, string range = (0, 9), width = 165.078, A/D/L = 15.48/6.12/0.54, glyph count = 9, runs = (
{string range = (0, 3), string = "\u54C8\u54C8\u54C8", attributes = {type = mutable dict, count = 2,
entries =>
0 : {contents = "NSColor"} = UIExtendedSRGBColorSpace 0 0 1 1
2 : {contents = "NSFont"} = {name = .PingFangSC-Regular, size = 18.000000, matrix = 0x0, descriptor = {attributes = {type = mutable dict, count = 1,
entries =>
2 : {contents = "NSFontNameAttribute"} = {contents = ".PingFangSC-Regular"}
}
>}}
}
}
{string range = (3, 6), string = "\uFF0C\u4F60\u662F\u505A\u68D2\u7684", attributes = {type = mutable dict, count = 1,
entries =>
2 : {contents = "NSFont"} = {name = .PingFangSC-Regular, size = 18.000000, matrix = 0x0, descriptor = {attributes = {type = mutable dict, count = 1,
entries =>
2 : {contents = "NSFontNameAttribute"} = {contents = ".PingFangSC-Regular"}
}
>}}
}
}
)
}
)
- 例二,给文本添加一个100宽,100长的空格.如图四所示。
#import
#define ATTRIBUTE_WIDTH @"AttributeWidth"
#define ATTRIBUTE_HEIGHT @"AttributeHeight"
@implementation MyTextLbl{
NSString * contentStr;
}
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
contentStr = @"哈哈哈,你是做棒的";
}
return self;
}
- (void)drawRect:(CGRect)rect{
[super drawRect:rect];
NSMutableDictionary * infoDic = [[NSMutableDictionary alloc] init];
[infoDic setObject:[UIFont systemFontOfSize:18] forKey:NSFontAttributeName];
NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:contentStr attributes:infoDic];
[attributeStr addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(0, 3)];
NSMutableAttributedString * attriImageStr = [self sepcailAttributeStringWith:100 height:100];
[attributeStr appendAttributedString:attriImageStr];
NSMutableAttributedString *attriOther = [[NSMutableAttributedString alloc] initWithString:@"我是尾巴,哈哈"];
[attributeStr appendAttributedString:attriOther];
//2 绘制 CFRangeMake(0, 0) 不设置区域
CTFramesetterRef setterRef = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathAddRect(pathRef, NULL, CGRectMake(0, -0, self.frame.size.width, self.frame.size.height));
CTFrameRef frameRef = CTFramesetterCreateFrame(setterRef, CFRangeMake(0, 0), pathRef, NULL);
CGContextRef context = UIGraphicsGetCurrentContext();
//调整坐标
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
//沿x轴移动了0个单位,沿y轴移动了self.bounds.size.height个单位
CGContextTranslateCTM(context, 0, self.bounds.size.height);
//旋转Y轴坐标
CGContextScaleCTM(context, 1, -1.0);
CTFrameDraw(frameRef, context);
NSArray * lineRefAry = (__bridge NSArray *)CTFrameGetLines(frameRef);
NSLog(@"%@",lineRefAry);
CFRelease(setterRef);
CFRelease(pathRef);
CFRelease(frameRef);
}
- (NSMutableAttributedString *)sepcailAttributeStringWith:(float)width height:(float)height{
CTRunDelegateCallbacks callBack;
//清空结构体中的值
memset(&callBack, 0, sizeof(CTRunDelegateCallbacks));
callBack.version = kCTRunDelegateVersion1;
//设置上行高度的回掉
callBack.getAscent = getAscentCallback;
//设置下行高度的回掉
callBack.getDescent = getDescentCallback;
//设置高度的回掉
callBack.getWidth = getWidthCallback;
//NSDictionary *runInfo = @{ATTRIBUTE_WIDTH : @(width),ATTRIBUTE_HEIGHT : @(height)};
NSDictionary *runInfo = [NSDictionary dictionaryWithObjectsAndKeys:@(width), ATTRIBUTE_WIDTH, @(height), ATTRIBUTE_HEIGHT, nil];
CTRunDelegateRef delegate = CTRunDelegateCreate(&callBack, (void *)runInfo);
NSMutableAttributedString * attriStr = [[NSMutableAttributedString alloc] initWithString:@" " attributes:nil];
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attriStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);
CFRelease(delegate);
return attriStr;
}
CGFloat getAscentCallback(void * refCon ){
NSDictionary *runInfo = (__bridge NSDictionary*)refCon;
return [[runInfo objectForKey:ATTRIBUTE_HEIGHT] floatValue];
}
CGFloat getDescentCallback(void * refCon ){
return 0;
}
CGFloat getWidthCallback(void * refCon ){
NSDictionary *runInfo = (__bridge NSDictionary*)refCon;
return [[runInfo objectForKey:ATTRIBUTE_WIDTH] floatValue];
}
@end
- 例三,选择空格的位置,将其填充为图片。如图五所示。
#import
#define ATTRIBUTE_WIDTH @"AttributeWidth"
#define ATTRIBUTE_HEIGHT @"AttributeHeight"
@implementation MyTextLbl{
NSString * contentStr;
NSInteger imageIndex;
float imageX;
float imageY;
UIImageView *_imageV;
}
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
contentStr = @"哈哈哈,你是做棒的";
}
return self;
}
- (void)layoutSubviews{
[super layoutSubviews];
if (!_imageV) {
_imageV = [[UIImageView alloc] init];
_imageV.image = [UIImage imageNamed:@"1.jpg"];
[self addSubview:_imageV];
}
[_imageV setFrame:CGRectMake(imageX, imageY, 100, 100)];
}
- (void)drawRect:(CGRect)rect{
[super drawRect:rect];
NSMutableDictionary * infoDic = [[NSMutableDictionary alloc] init];
[infoDic setObject:[UIFont systemFontOfSize:18] forKey:NSFontAttributeName];
NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:contentStr attributes:infoDic];
[attributeStr addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(0, 3)];
imageIndex = attributeStr.length;
NSMutableAttributedString * attriImageStr = [self sepcailAttributeStringWith:100 height:100];
[attributeStr appendAttributedString:attriImageStr];
NSMutableAttributedString *attriOther = [[NSMutableAttributedString alloc] initWithString:@"我是尾巴,哈哈"];
[attributeStr appendAttributedString:attriOther];
//2 绘制 CFRangeMake(0, 0) 不设置区域
CTFramesetterRef setterRef = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathAddRect(pathRef, NULL, CGRectMake(0, -0, self.frame.size.width, self.frame.size.height));
CTFrameRef frameRef = CTFramesetterCreateFrame(setterRef, CFRangeMake(0, 0), pathRef, NULL);
CGContextRef context = UIGraphicsGetCurrentContext();
//调整坐标
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
//沿x轴移动了0个单位,沿y轴移动了self.bounds.size.height个单位
CGContextTranslateCTM(context, 0, self.bounds.size.height);
//旋转Y轴坐标
CGContextScaleCTM(context, 1, -1.0);
CTFrameDraw(frameRef, context);
//xuan
NSArray * lineRefAry = (__bridge NSArray *)CTFrameGetLines(frameRef);
//文本的总高度
double heightAddUp = 0;//line的总高度
for (NSInteger i = 0; i < lineRefAry.count; i++) {
CTLineRef lineRef = (__bridge CTLineRef)lineRefAry[i];
//上行高度
CGFloat ascent;
//下行高度
CGFloat descent;
//行间距
CGFloat lineGap;
CTLineGetTypographicBounds(lineRef, &ascent, &descent, &lineGap);
NSArray * runAry = (__bridge NSArray *)CTLineGetGlyphRuns(lineRef);
double startX = 0;
for (NSInteger j = 0; j < runAry.count; j ++) {
CTRunRef runRef = (__bridge CTRunRef)runAry[j];
CFRange runRange = CTRunGetStringRange(runRef);
if (imageIndex >= runRange.location && imageIndex < runRange.location + runRange.length) {
NSLog(@"imageIndex = %lu location = %lu length = %lu",imageIndex,runRange.location,runRange.length);
imageY = heightAddUp;
imageX = startX;
NSDictionary *infoDict = (__bridge NSDictionary*)CTRunGetAttributes(runRef);//kCTRunDelegateAttributeName
CTRunDelegateRef runDelegate = (__bridge CTRunDelegateRef)[infoDict objectForKey:@"CTRunDelegate"];
NSDictionary *frameInfo = CTRunDelegateGetRefCon(runDelegate);
NSLog(@"frameInfo = %@",frameInfo);
}
double runWidth = CTRunGetTypographicBounds(runRef, CFRangeMake(0, 0), 0, 0, 0);
startX += runWidth;
}
heightAddUp += ascent + fabs(descent) + lineGap;
}
[self setNeedsLayout];
CFRelease(setterRef);
CFRelease(pathRef);
CFRelease(frameRef);
}
- (NSMutableAttributedString *)sepcailAttributeStringWith:(float)width height:(float)height{
CTRunDelegateCallbacks callBack;
//清空结构体中的值
memset(&callBack, 0, sizeof(CTRunDelegateCallbacks));
callBack.version = kCTRunDelegateVersion1;
//设置上行高度的回掉
callBack.getAscent = getAscentCallback;
//设置下行高度的回掉
callBack.getDescent = getDescentCallback;
//设置高度的回掉
callBack.getWidth = getWidthCallback;
//NSDictionary *runInfo = @{ATTRIBUTE_WIDTH : @(width),ATTRIBUTE_HEIGHT : @(height)};
NSDictionary *runInfo = [NSDictionary dictionaryWithObjectsAndKeys:@(width), ATTRIBUTE_WIDTH, @(height), ATTRIBUTE_HEIGHT, nil];
CTRunDelegateRef delegate = CTRunDelegateCreate(&callBack, (void *)runInfo);
NSMutableAttributedString * attriStr = [[NSMutableAttributedString alloc] initWithString:@" " attributes:nil];
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attriStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);
CFRelease(delegate);
return attriStr;
}
CGFloat getAscentCallback(void * refCon ){
NSDictionary *runInfo = (__bridge NSDictionary*)refCon;
return [[runInfo objectForKey:ATTRIBUTE_HEIGHT] floatValue];
}
CGFloat getDescentCallback(void * refCon ){
return 0;
}
CGFloat getWidthCallback(void * refCon ){
NSDictionary *runInfo = (__bridge NSDictionary*)refCon;
return [[runInfo objectForKey:ATTRIBUTE_WIDTH] floatValue];
}
@end