CoreText

CoreText的简单使用(三)

2021-06-18  本文已影响0人  KG丿夏沫

基于前面文章<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>

上一篇 下一篇

猜你喜欢

热点阅读