美文网首页CoreText
CoreText的简单使用(三)

CoreText的简单使用(三)

作者: KG丿夏沫 | 来源:发表于2021-06-18 09:11 被阅读0次

    基于前面文章<a href="https://juejin.cn/user/430664724781693">《CoreText的简单使用(二)》</a>的介绍,我们基于CoreText封装了一个简单的文本排版框架,但是它有很大的局限性,因为我们平时开发的时候,如果需要文本排版,很大可能性就是需要一段文本中展示不同样式,可能有多种字体、颜色、大小等。所以基于之前的框架基础上再次进行修改。

    定制排版文件格式

    我们首先想到对KGCTFrameParser进行修改,因为我们使用UILabel加载富文本的时候,就是使用NSAttributeString来实现文本变色、字体大小变化等。所以我们修改KGCTFrameParser类的方法,使用NSAttributeString作为参数。

    修改后KGCTFrameParser类代码如下:

    #import <Foundation/Foundation.h>
    #import "KGCoreTextData.h"
    #import "KGCTFrameParserConfig.h"
    
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface KGCTFrameParser : NSObject
    
    + (KGCoreTextData *)parseContent:(NSAttributedString *)content config:(KGCTFrameParserConfig *)config;
    
    + (NSDictionary *)attributesWithConfig:(KGCTFrameParserConfig *)config;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "KGCTFrameParser.h"
    
    @implementation KGCTFrameParser
    
    + (NSDictionary *)attributesWithConfig:(KGCTFrameParserConfig *)config{
        CGFloat fontSize = config.fontSize;
        CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
        CGFloat lineSpacing = config.lineSpace;
        const CFIndex kNumberOfSettings = 3;
        CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
            {kCTParagraphStyleSpecifierLineSpacingAdjustment,sizeof(CGFloat),&lineSpacing},
            {kCTParagraphStyleSpecifierMaximumLineSpacing,sizeof(CGFloat),&lineSpacing},
            {kCTParagraphStyleSpecifierMinimumLineSpacing,sizeof(CGFloat),&lineSpacing}
        };
        CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);
        
        UIColor *textColor = config.textColor;
        
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        dict[NSForegroundColorAttributeName] = (id)textColor.CGColor;
        dict[NSFontAttributeName] = (__bridge id)fontRef;
        dict[NSParagraphStyleAttributeName] = (__bridge id)theParagraphRef;
        
        CFRelease(theParagraphRef);
        CFRelease(fontRef);
        return dict;
    }
    
    + (KGCoreTextData *)parseContent:(NSAttributedString *)content config:(KGCTFrameParserConfig *)config{
        //创建CTFramesetterRef实例
        CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)content);
        //获得要绘制的区域的高度
        CGSize restrictSize = CGSizeMake(config.width, CGFLOAT_MAX);
        CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, restrictSize, nil);
        CGFloat textHeight = coreTextSize.height;
        
        //生成CTFrameRef实例
        CTFrameRef frame = [self createFrameWithFrameSetter:frameSetter config:config height:textHeight];
        
        //将生成好的CTFrameRef实例和计算好的绘制高度保存到CoreTextData实例中,最后返回实例
        KGCoreTextData *data = [[KGCoreTextData alloc] init];
        data.ctFrame = frame;
        data.height = textHeight;
        CFRelease(frame);
        CFRelease(frameSetter);
        return data;
    }
    
    + (CTFrameRef)createFrameWithFrameSetter:(CTFramesetterRef)frameSetter config:(KGCTFrameParserConfig *)config height:(CGFloat)height{
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathAddRect(path, NULL, CGRectMake(0, 0, config.width, height));
        CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
        CFRelease(path);
        return frame;
    }
    
    @end
    

    然后在KGCoreTextCtrl中进行配置,代码如下:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.view.backgroundColor = [UIColor whiteColor];
        
        KGCTFrameParserConfig *config = [[KGCTFrameParserConfig alloc] init];
        config.textColor = [UIColor blackColor];
        config.width = self.ctView.width;
        
        NSString *str = @"这是一个测试代码,主要是为了展示富文本,前面一句话显示红色,第二句话显示绿色,三句话字体放大";
        NSDictionary *attr = [KGCTFrameParser attributesWithConfig:config];
        
        NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:str attributes:attr];
    
        [attributeString addAttributes:@{NSForegroundColorAttributeName:[UIColor redColor]} range:NSMakeRange(0, 9)];
        [attributeString addAttributes:@{NSForegroundColorAttributeName:[UIColor greenColor]} range:NSMakeRange(10, 10)];
        [attributeString addAttributes:@{NSForegroundColorAttributeName:[UIColor blueColor],NSFontAttributeName:KGFont(20.0)} range:NSMakeRange(21, 10)];
        
        KGCoreTextData *data = [KGCTFrameParser parseContent:attributeString config:config];
        
        self.ctView.data = data;
        self.ctView.height = data.height;
        self.ctView.backgroundColor = [UIColor yellowColor];
    }
    
    - (KGDisPlayView *)ctView{
        if (!_ctView) {
            self.ctView = [[KGDisPlayView alloc] initWithFrame:CGRectMake(50, 100, self.view.frame.size.width - 100, 300)];
            [self.view addSubview:self.ctView];
        }
        return _ctView;
    }
    

    最后运行项目,查看效果,如下图所示:

    20200810003.png

    但是在实际开发的时候,不会只是简简单单显示这种,而且前后端数据交互啥的,我们拿到是这种的数据的话,处理起来很麻烦,所以一般都是和后台进行约定,规定一种格式,方便前端进行使用,最常见的就是JSON格式直接返回排版需要的配置,例如:

    [
        {
            "color":"red",
            "size":"12",
            "content":"但是在实际开发的时候,不会只是简简单单显示这种,而且前后端数据交互啥的",
            "type":"txt"
        },
        {
            "color":"green",
            "size":"16",
            "content":"我们拿到是这种的数据的话,处理起来很麻烦,所以一般都是和后台进行约定",
            "type":"txt"
        },
        {
            "color":"blue",
            "size":"20",
            "content":"规定一种格式,方便前端进行使用,最常见的就是JSON格式直接返回排版需要的配置",
            "type":"txt"
        }
    ]
    

    我以前做一个类似微博广场功能块的图文混排时候,后台返回的就是这种数据类型,当我们得到这样的JSON数据后,我们可以使用优秀的三方库进行数据解析JSONKit,也可以通过系统提供的NSJSONSerialization进行解析,得到NSDictionary对象的数据组后,通过以下一系类方法进行转换得到我们绘制需要的数据。

    首先我们需要将接受到的JSON数据进行解析,然后读取JSON数据返回的配置以及内容,得到一个富文本,然后根据富文本去生成绘制模型,具体代码如下:

    #pragma mark --根据JSON数据,创建富文本
    
    + (NSDictionary *)attributesWithConfig:(KGCTFrameParserConfig *)config{
        CGFloat fontSize = config.fontSize;
        CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
        CGFloat lineSpacing = config.lineSpace;
        const CFIndex kNumberOfSettings = 3;
        CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
            {kCTParagraphStyleSpecifierLineSpacingAdjustment,sizeof(CGFloat),&lineSpacing},
            {kCTParagraphStyleSpecifierMaximumLineSpacing,sizeof(CGFloat),&lineSpacing},
            {kCTParagraphStyleSpecifierMinimumLineSpacing,sizeof(CGFloat),&lineSpacing}
        };
        CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);
        
        UIColor *textColor = config.textColor;
        
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        dict[NSForegroundColorAttributeName] = (id)textColor.CGColor;
        dict[NSFontAttributeName] = (__bridge id)fontRef;
        dict[NSParagraphStyleAttributeName] = (__bridge id)theParagraphRef;
        
        CFRelease(theParagraphRef);
        CFRelease(fontRef);
    
        return dict;
    }
    
    + (CTFrameRef)createFrameWithFrameSetter:(CTFramesetterRef)frameSetter config:(KGCTFrameParserConfig *)config height:(CGFloat)height{
        //创建一个可变路径,图形上下文中要绘制的图形或线条的数学描述
        CGMutablePathRef path = CGPathCreateMutable();
        //追加矩形到可变路径
        CGPathAddRect(path, NULL, CGRectMake(0, 0, config.width, height));
        //对核心文本框架对象的引用
        CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, NULL);
        CFRelease(path);
        return frame;
    }
    
    + (UIColor *)colorFromDictionary:(NSDictionary *)dict{
        if ([dict[@"color"] isEqualToString:@"red"]) {
            return [UIColor redColor];
        } else if ([dict[@"color"] isEqualToString:@"blue"]) {
            return [UIColor blueColor];
        } else if ([dict[@"color"] isEqualToString:@"green"]) {
            return [UIColor greenColor];
        }else{
            return nil;
        }
    }
    
    + (NSAttributedString *)attributedingWithDictionary:(NSDictionary *)dic config:(KGCTFrameParserConfig *)config{
        //创建一个可变字典,保存默认富文本信息配置选项
        NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithDictionary:[self attributesWithConfig:config]];
        //获取数据中颜色值
        UIColor *color = [self colorFromDictionary:dic];
        if (color) {
            //如果数据中给出颜色值,替换默认设置的色值
            attributes[NSForegroundColorAttributeName] = color;
        }
        CGFloat fontSize = [dic[@"size"] floatValue];
        if (fontSize > 0) {
            //获取数据返回的字体大小,如果存在,创建一个CTFontRef实例,替换默认设置项中的字体设置
            CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
            attributes[NSFontAttributeName] = (__bridge id)fontRef;
            //释放CTFontRef实例
            CFRelease(fontRef);
        }
        NSString *content = dic[@"content"];
        //根据富文本培训选项以及给定字符串创建并返回一个富文本
        return [[NSAttributedString alloc] initWithString:content attributes:attributes];
    }
    
    + (NSAttributedString *)loadJSONContent:(NSString *)jsonContent config:(KGCTFrameParserConfig *)config{
        //JSON字符串转NSData
        NSData *data = [jsonContent dataUsingEncoding:NSUTF8StringEncoding];
        //创建一个可变富文本,保存JSON数据
        NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init];
        //这里做下容错处理,如果传入的json数据是非空并且有内容,进行数据转换
        if (data) {
            //因为JSON数据本身最外层就是一个数组,所以这块需要用数组去接受JSON解析后得到的值
            NSArray *arr = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
            //这里再做下容错处理,如果是NSArray类,那就说明数据没有问题,可以进行下一步操作
            if ([arr isKindOfClass:[NSArray class]]) {
                //直接使用for in遍历
                for (NSDictionary *dict in arr) {
                    NSString *type = dict[@"type"];
                    //判断type类型是否是txt,因为现在只是对txt类型进行的处理,所以这块需要进行过滤
                    if ([type isEqualToString:@"txt"]) {
                        //获取到单条数据富文本信息
                        NSAttributedString *as = [self attributedingWithDictionary:dict config:config];
                        //将单条数据富文本信息拼接到总数据中
                        [result appendAttributedString:as];
                    }
                }
            }
        }
        return result;
    }
    
    + (KGCoreTextData *)parseJSONContent:(NSString *)jsonContent config:(KGCTFrameParserConfig *)config{
        //通过JSON数据,得到富文本
        NSAttributedString *content = [self loadJSONContent:jsonContent config:config];
        //创建CTFramesetterRef实例
        CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)content);
        //设置绘制边界大小
        CGSize restrictSize = CGSizeMake(config.width, CGFLOAT_MAX);
        //获得实际绘制所需要的大小
        CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, restrictSize, nil);
        //因为宽度已经国定,所以在这获取实际绘制需要的高度
        CGFloat textHeight = coreTextSize.height;
        //生成CTFrameRef实例
        CTFrameRef frame = [self createFrameWithFrameSetter:frameSetter config:config height:textHeight];
        //将生成好的CTFrameRef实例和计算好的绘制高度保存到CoreTextData实例中,最后返回实例
        KGCoreTextData *data = [[KGCoreTextData alloc] init];
        //设置实际绘制需要的实例对象
        data.ctFrame = frame;
        //设置实际绘制需要的高度
        data.height = textHeight;
        //因为底层库不受ARC约束,所以需要手动调用CFRelease来进行释放
        //释放生成的CTFrameRef实例
        CFRelease(frame);
        //释放CTFramesetterRef实例
        CFRelease(frameSetter);
        //返回需要的绘制数据模型
        return data;
    }
    

    方法里面的注释写的很清晰,所以不做多的介绍。我们在KGDisPlayView内部的设置还是不变,只需要在ViewController中对KGCTFrameParser进行配置,代码如下:

    #import "KGCoreTextCtrl.h"
    #import "KGDisPlayView.h"
    #import "KGCTFrameParserConfig.h"
    #import "KGCoreTextData.h"
    #import "KGCTFrameParser.h"
    #import <CoreText/CoreText.h>
    
    @interface KGCoreTextCtrl ()
    
    @property (nonatomic, strong) KGDisPlayView *ctView;
    
    @end
    
    @implementation KGCoreTextCtrl
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.view.backgroundColor = [UIColor whiteColor];
        
        KGCTFrameParserConfig *config = [[KGCTFrameParserConfig alloc] init];
        config.textColor = [UIColor blackColor];
        config.width = self.ctView.width;
        
        NSArray *arr = @[
        @{
            @"type":@"txt",
            @"content":@"但是在实际开发的时候,不会只是简简单单显示这种,而且前后端数据交互啥的,",
            @"size":@"12",
            @"color":@"red"
        },
        @{
            @"type":@"txt",
            @"content":@"我们拿到是这种的数据的话,处理起来很麻烦,所以一般都是和后台进行约定,",
            @"size":@"16",
            @"color":@"green"
        },
        @{
            @"type":@"txt",
            @"content":@"规定一种格式,方便前端进行使用,最常见的就是JSON格式直接返回排版需要的配置",
            @"size":@"20",
            @"color":@"blue"
        }
        ];
        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:arr options:NSJSONWritingFragmentsAllowed error:nil];
        KGCoreTextData *data = [KGCTFrameParser parseJSONContent:[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] config:config];
        
        self.ctView.data = data;
        self.ctView.height = data.height;
        self.ctView.backgroundColor = [UIColor yellowColor];
    }
    
    - (KGDisPlayView *)ctView{
        if (!_ctView) {
            self.ctView = [[KGDisPlayView alloc] initWithFrame:CGRectMake(50, 100, self.view.frame.size.width - 100, 300)];
            [self.view addSubview:self.ctView];
        }
        return _ctView;
    }
    
    
    @end
    

    最后得到的效果如下图所示:

    20200811001.png

    到此我们的框架可以支持富文本格式的文本渲染了,下一篇,开始探索图文混排。

    系列文章:

    <a href="https://juejin.cn/post/6970879379425460255">《CoreText的简单使用(一)》</a>

    <a href="https://juejin.cn/user/430664724781693">《CoreText的简单使用(二)》</a>

    <a href="https://juejin.cn/post/6970880935327694879/">《CoreText的简单使用(三)》</a>

    <a href="https://juejin.cn/post/6970881236936081439/">《CoreText的简单使用(四)》</a>

    <a href="https://juejin.cn/post/6970881873304092686/">《CoreText的简单使用(五)》</a>

    相关文章

      网友评论

        本文标题:CoreText的简单使用(三)

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