iOS 图文混排(富文本)

富文本绘制步骤

  1. 首先需要一个 StringA;
  2. 把 StringA 转成 attributeString,并添加相关样式;
  3. 生成 CTFramessetter,得到 CTFrame;
  4. 绘制 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;
}

你可能感兴趣的:(iOS 图文混排(富文本))