美文网首页
iOS 图文混排(富文本)

iOS 图文混排(富文本)

作者: cgfloat | 来源:发表于2019-03-19 17:49 被阅读0次

    富文本绘制步骤

    1. 首先需要一个 StringA;
    2. 把 StringA 转成 attributeString,并添加相关样式;
    3. 生成 CTFramessetter,得到 CTFrame;
    4. 绘制 CTFrameDraw。

    绘制完成后,因为绘制只是显示,其他的需要额外操作。
    响应相关点击事件原理:
    CTFrame 包含了多个 CTLine,并且可以得到每个 line 的起始位置和大小,计算出响应的区域范围,然后根据点击坐标来判断是否在响应区。
    又如图片显示原理:
    先用空白占位符将位置保留出来,然后再添加图片和其他。

    富文本绘制需要引入框架 #import <CoreText/CoreText.h>

    自定义 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<UITouch *> *)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 图文混排(富文本)

          本文链接:https://www.haomeiwen.com/subject/ykbgmqtx.html