CTFrame作为一个整体的画布,其中由行(CTLine)组成,每行可以分为一个或多个小方块(CTRun),属性一样的字符就分在一个小方块里。
因为绘制只是显示,其他需要的额外操作,如响应相关点击事件原理:CTFrame包含了多个CTLine,并且可以得到每个line的起始位置与大小,计算出你响应的区域范围,然后根据你点击的坐标来判断是否在响应区。再如图片显示原理:先用空白占位符来把位置留出来,然后再添加图片
富文本绘制
富文本绘制步骤:
- 先需要一个StringA
- 把StringA转换成attributeString,并添加相关样式
- 生成CTFramesetter,得到CTFrame
- 绘制CTFrameDraw
@interface EOCTextLabel() {
NSRange sepRange;
CGRect sepRect;
}
@end
@implementation EOCTextLabel
- (void)drawRect:(CGRect)rect {
sepRange = NSMakeRange(30, 5);
NSMutableAttributedString *attriStr = [[NSMutableAttributedString alloc] initWithString:self.text attributes:nil];
[attriStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16] range:NSMakeRange(0, self.text.length)];
[attriStr addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:sepRange];
CGContextRef context = UIGraphicsGetCurrentContext();
//生成frame
CTFramesetterRef frameset = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attriStr);
CGPathRef path = CGPathCreateWithRect(CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), &CGAffineTransformIdentity);
CTFrameRef frame = CTFramesetterCreateFrame(frameset, CFRangeMake(0, 0), path, nil);
//调整坐标
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.frame.size.height);
CGContextScaleCTM(context, 1, -1);
//绘制
CTFrameDraw(frame, context);
}
事件添加
事件添加步骤
- CTFrame中遍历CTLine,CTLine中遍历CTRun
- 获得CTRun的位置判断其是否在CFRange范围内,寻找出指定位置的startX和startY
- 根据寻找出的x和y组成其位置rect
- 判断事件的点击位置是否处于rect中,如果是则触发事件
- (void)drawRect:(CGRect)rect { //...绘制 //获取信息 NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame); CGPoint pointArray[lines.count]; memset(pointArray, 0, sizeof(pointArray)); //由于坐标系的关系,不直接通过这种方式拿行(CTLine)的起始位置 CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), pointArray); double heightAddup = 0; //CTLine信息获取 for (int i=0; i<lines.count; i++) { CTLineRef line = (__bridge CTLineRef)lines[i]; NSArray *runs = (__bridge NSArray *)CTLineGetGlyphRuns(line); CGFloat ascent = 0; CGFloat descent = 0; CGFloat lineGap = 0; CTLineGetTypographicBounds(line, &ascent, &descent, &lineGap); double runHeight = ascent + descent + lineGap; double startX = 0; //CTRun信息获取 for (int j=0; j<runs.count; j++) { CTRunRef run = (__bridge CTRunRef)runs[j]; CFRange runRange = CTRunGetStringRange(run); double runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), 0, 0, 0); if (runRange.location == sepRange.location && runRange.length == sepRange.length) { //计算我们需要的位置和size,即rect NSLog(@"找到了"); NSLog(@"%f, %f, %f, %f", startX, heightAddup, runWidth, runHeight); sepRect = CGRectMake(startX, heightAddup, runWidth, runHeight); //只有点击第三个字符时才会触发 //sepRect = CGRectMake(startX+runWidth*2/5, heightAddup, runWidth/5, runHeight); } startX += runWidth; } //字的高度叠加 heightAddup += runHeight; NSLog(@"%f====%f", pointArray[i].y, heightAddup); } //添加button按钮和事件也可以达到需求要求 [self setNeedsLayout]; } - (void)layoutSubviews { if (sepRect.size.width>0) { } } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; if (CGRectContainsPoint(sepRect, point)) { NSLog(@"点击"); } }
图文混排
图文混排步骤
- 定义attributeString,里边包含占位符及其宽高等属性
- 根据attributeString绘制
- 遍历frame,line,run找出占位符的坐标及大小
- 调用[self setNeedsLayout]后自动layoutSubviews,设置ImageView的位置和图片
#define EOCCoreTextImageWidthPro @"EOCCoreTextImageWidthPro" #define EOCCoreTextImageHeightPro @"EOCCoreTextImageHeightPro" static CGFloat ctRunDelegateGetWidthCallback(void *refCon) { NSDictionary *infoDic = (__bridge NSDictionary *)refCon; if ([infoDic isKindOfClass:[NSDictionary class]]) { return [infoDic[EOCCoreTextImageWidthPro] floatValue]; } return 0; } static CGFloat ctRunDelegateGetAscentCallback(void *refCon) { NSDictionary *infoDic = (__bridge NSDictionary *)refCon; if ([infoDic isKindOfClass:[NSDictionary class]]) { return [infoDic[EOCCoreTextImageHeightPro] floatValue]; } return 0; } static CGFloat ctRunDelegateGetDescentCallback(void *refCon) { return 0; } @interface EOCImageLabel() { NSInteger ImageSpaceIndex; CGRect sepRect; UIImageView *_eocImageV; } @end @implementation EOCImageLabel
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
ImageSpaceIndex = self.text.length;
NSMutableAttributedString *attriStr = [[NSMutableAttributedString alloc] initWithString:self.text attributes:nil];
[attriStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16] range:NSMakeRange(0, self.text.length)];
//图片占位符添加
NSMutableAttributedString *attriImageSpaceStr = [self sepImageSpaceWithWidth:100 height:50];
[attriStr appendAttributedString:attriImageSpaceStr];
NSMutableAttributedString *attriTailStr = [[NSMutableAttributedString alloc] initWithString:@"123456789" attributes:nil];
[attriStr appendAttributedString:attriTailStr];
CGContextRef context = UIGraphicsGetCurrentContext();
//生成frame
CTFramesetterRef frameset = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attriStr);
CGPathRef path = CGPathCreateWithRect(CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), &CGAffineTransformIdentity);
CTFrameRef frame = CTFramesetterCreateFrame(frameset, CFRangeMake(0, 0), path, nil);
//调整坐标
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.frame.size.height);
CGContextScaleCTM(context, 1, -1);
//绘制
CTFrameDraw(frame, context);
//获取信息
NSArray *lines = (__bridge NSArray *)CTFrameGetLines(frame);
CGPoint pointArray[lines.count];
memset(pointArray, 0, sizeof(pointArray));
//由于坐标系的关系,不直接通过这种方式拿行(CTLine)的起始位置
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), pointArray);
double heightAddup = 0;
//CTLine信息获取
for (int i=0; i<lines.count; i++) {
CTLineRef line = (__bridge CTLineRef)lines[i];
NSArray *runs = (__bridge NSArray *)CTLineGetGlyphRuns(line);
CGFloat ascent = 0;
CGFloat descent = 0;
CGFloat lineGap = 0;
CTLineGetTypographicBounds(line, &ascent, &descent, &lineGap);
double runHeight = ascent + descent + lineGap;
double startX = 0;
//CTRun信息获取
for (int j=0; j<runs.count; j++) {
CTRunRef run = (__bridge CTRunRef)runs[j];
CFRange runRange = CTRunGetStringRange(run);
double runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), 0, 0, 0);
if (ImageSpaceIndex==runRange.location && ImageSpaceIndex < runRange.location + runRange.length) {
//计算我们需要的位置和size,即rect
NSLog(@"找到了");
NSLog(@"%f, %f, %f, %f", startX, heightAddup, runWidth, runHeight);
sepRect = CGRectMake(startX, heightAddup, runWidth, runHeight);
//只有点击第三个字符时才会触发
//sepRect = CGRectMake(startX+runWidth*2/5, heightAddup, runWidth/5, runHeight);
}
startX += runWidth;
}
//字的高度叠加
heightAddup += runHeight;
NSLog(@"%f====%f", pointArray[i].y, heightAddup);
}
//添加button按钮和事件也可以达到需求要求
[self setNeedsLayout];
}
- (void)layoutSubviews {
if (sepRect.size.width>0) {
_eocImageV = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"0.png"]];
[self addSubview:_eocImageV];
}
_eocImageV.frame = sepRect;
}
- (NSMutableAttributedString *)sepImageSpaceWithWidth:(float)width height:(float)height {
//创建占位符
NSMutableAttributedString *spaceAttribut = [[NSMutableAttributedString alloc] initWithString:@" " attributes:nil];
//配置占位符的属性
CTRunDelegateCallbacks callbacks;
memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));
callbacks.getAscent = ctRunDelegateGetAscentCallback;
callbacks.getDescent = ctRunDelegateGetDescentCallback;
callbacks.getWidth = ctRunDelegateGetWidthCallback;
callbacks.version = kCTRunDelegateCurrentVersion;
static NSMutableDictionary *argDic = nil;
argDic = [[NSMutableDictionary alloc] init];
[argDic setValue:@(width) forKey:EOCCoreTextImageWidthPro];
[argDic setValue:@(height) forKey:EOCCoreTextImageHeightPro];
CTRunDelegateRef runDelegate = CTRunDelegateCreate(&callbacks, (__bridge void*)argDic);
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)spaceAttribut, CFRangeMake(0, 1), kCTRunDelegateAttributeName, runDelegate);
return spaceAttribut;
}
原文:大专栏 图文混排原理实现及应用
原文地址:https://www.cnblogs.com/chinatrump/p/11614899.html
时间: 2024-11-06 07:34:20