图文混排原理实现及应用

Posted petewell

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图文混排原理实现及应用相关的知识,希望对你有一定的参考价值。

技术图片
技术图片
技术图片

CTFrame作为一个整体的画布,其中由行(CTLine)组成,每行可以分为一个或多个小方块(CTRun),属性一样的字符就分在一个小方块里。

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

富文本绘制

富文本绘制步骤:

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

事件添加

事件添加步骤

  1. CTFrame中遍历CTLine,CTLine中遍历CTRun
  2. 获得CTRun的位置判断其是否在CFRange范围内,寻找出指定位置的startX和startY
  3. 根据寻找出的x和y组成其位置rect
  4. 判断事件的点击位置是否处于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(@"点击");
        
    
    

图文混排

图文混排步骤

  1. 定义attributeString,里边包含占位符及其宽高等属性
  2. 根据attributeString绘制
  3. 遍历frame,line,run找出占位符的坐标及大小
  4. 调用[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;

原文:大专栏  图文混排原理实现及应用


以上是关于图文混排原理实现及应用的主要内容,如果未能解决你的问题,请参考以下文章

UGUI-图文混排方案

iOS 表情键盘+gif聊天图文混排,看我的就够了

Button实现图文混排

Office操作:图文混排三步曲

CoreText实现图文混排之点击事件

IOS开发之使用UIWebView实现图文混排