图文混排原理实现及应用



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;
}

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

原文地址:https://www.cnblogs.com/chinatrump/p/11614899.html

时间: 2024-11-06 07:34:20

图文混排原理实现及应用的相关文章

关于仿网易新闻中详细页图文混排功能的实现

最近在了解关于新闻内容的图文混排的效果,网上有人开源一个仿网易新闻的代码,本文就是简单记录学习其详细页面显示的效果实现: 下载地址:https://github.com/dsxNiubility/SXNews 效果图: 其原理:通过网络请求获得相关的信息,再通过手机端进行拼HTML,然后在WebView进行展示,此处还对文章中的图片增加点击效果,可以保存到相册中:文章的样式已经存在项目中,直接去调用: 1:首先了解两个相关的实体对象,一个是新闻的主体内容,另外一个就是图片的相关信息实体: 1:主

IOS开发UI篇--一个支持图文混排的ActionSheet

一.简单介绍 UIActionSheet是IOS提供给我们开发人员的底部弹出菜单控件.一般用于菜单选择.操作确认.删除确认等功能.IOS官方提供的下面方式对UIActionView进行实例化: - (instancetype)initWithTitle:(NSString *)title delegate:(id<UIActionSheetDelegate>)delegate cancelButtonTitle:(NSString *)cancelButtonTitle destructive

iOS 图文混排 链接 可点击

对于这个话题 我想到 1 第一个解决方法就是使用 webView 比较经典 把所有复杂工作都交给控件本身去处理了,  但是好像好多需要自定义的地方 没法从 webView获得响应回调 :(估计也可以实现 也比较复杂,而且 这个需要对 html编码进行分析理解剥离等) 2 富文本方式 核心框架 coretext 图文混排 一点问题都没有 关键是怎么对 目标图片 或者链接 进行触发响应 要点: (1)首先要封装的要相对独立 拓展也方便  首当其冲就是  和服务端约定的 数据模型 CoreTextMo

[Swift通天遁地]八、媒体与动画-(13)CoreText框架实现图文混排

本文将演示CoreText框架实现图文混排.CoreText(富文本)框架并不支持图片的绘制, 需要借助Core Graphics框架来进行图片的绘制. 图文混排的实现原理非常简单,就是在一个富文本中插入一个占位符, 表示此处需要插入一张图片.然后再由另一个图形绘制框架, 在占位符所在位置绘制指定的图片. 在项目文件夹上点击鼠标右键,弹出右键菜单. [New File]->[Cocoa Touch]->[Next]-> [Class]:CTImageView [Subclass of]:

Laya的位图文字,高亮文字,图文混排

测试版本:Laya 2.1.1.1 位图文字 白鹭的位图文字是由TextureMerger制作,然后在exml里使用. Laya的则直接使用FontClip组件. 在编辑模式,层级窗口中右键,选择创建组件UI,选择FontClip 将美术提供的位图文字赋值给FontClip的属性面板的skin属性,这样就是个位图文字了.比白鹭要方便些. 高亮文字 白鹭的高亮文字有文本样式 tx.textFlow = <Array<egret.ITextElement>>[ {text: "

(一一一)图文混排基础 -利用正则分割和拼接属性字符串

很多时候需要用到图文混排,例如聊天气泡中的表情,空间.微博中的表情,例如下图: 红心和文字在一起. 比较复杂的情况是表情夹杂在文字之间. 要实现这种功能,首先要介绍iOS中用于显示属性文字的类. 用于文字显示的类除了text属性之外,还有attributedText属性,这个属性是NSAttributedString类型,通过这个属性可以实现不同文字的不同字体.颜色甚至把图片作为文字显示的功能. 下面介绍这个字符串的使用. 以一条微博内容为应用场景,介绍如何从中找出表情.话题等内容,其中表情替换

经验之谈—正則表達式实现图文混排

在项目中,我们常常须要发表情,以及常常须要将表情字符转换成表情.由于表情是一个图片.所以我们发给server的时候,实际上是发一段特殊的文字给server,然后转换成表情.以免浪费用户过多的流量. 那接下来.我们就来介绍一下,怎样使用正則表達式实现图文混排呢? 为了以后的代码的管理方便,我们抽取出两个类: NSString+Regular.h中.我们暴露两个方法出来: /** * 返回正則表達式匹配的第一个结果 * * @param pattern 正則表達式 * * @return 匹配的第一

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

CoreText实现图文混排之点击事件 主要思路 我们知道,CoreText是基于UIView去绘制的,那么既然有UIView,就有 -(void)touchesBegan:(NSSet<UITouch *> )touches withEvent:(UIEvent )event方法,我们呢,就是基于这个方法去做点击事件的. 通过touchBegan方法拿到当前点击到的点,然后通过坐标判断这个点是否在某段文字上,如果在则触发对应事件. 上面呢就是主要思路.接下来呢,我们来详细讲解一下.还是老规矩

正则表达式之图文混排

目的 : 实现一句话中既有文字又有图片的功能. 图文混排又称富文本. 直接代码演示:   NSAttributedString+Emoji.h 中 :  1 #import <Foundation/Foundation.h> 2 3 @interface NSAttributedString (Emoji) 4 5 + (NSAttributedString *)emojiStringWithString:(NSMutableAttributedString *)emojiString; 6