基于前面文章<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>
网友评论