基于前面文章<a >《CoreText的簡(jiǎn)單使用(二)》</a>的介紹舀射,我們基于CoreText封裝了一個(gè)簡(jiǎn)單的文本排版框架代咸,但是它有很大的局限性,因?yàn)槲覀兤綍r(shí)開(kāi)發(fā)的時(shí)候套媚,如果需要文本排版,很大可能性就是需要一段文本中展示不同樣式磁椒,可能有多種字體、顏色玫芦、大小等浆熔。所以基于之前的框架基礎(chǔ)上再次進(jìn)行修改。
定制排版文件格式
我們首先想到對(duì)
KGCTFrameParser
進(jìn)行修改桥帆,因?yàn)槲覀兪褂肬ILabel加載富文本的時(shí)候医增,就是使用NSAttributeString來(lái)實(shí)現(xiàn)文本變色、字體大小變化等老虫。所以我們修改KGCTFrameParser類(lèi)的方法叶骨,使用NSAttributeString作為參數(shù)。
修改后
KGCTFrameParser
類(lèi)代碼如下:
#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{
//創(chuàng)建CTFramesetterRef實(shí)例
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)content);
//獲得要繪制的區(qū)域的高度
CGSize restrictSize = CGSizeMake(config.width, CGFLOAT_MAX);
CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, restrictSize, nil);
CGFloat textHeight = coreTextSize.height;
//生成CTFrameRef實(shí)例
CTFrameRef frame = [self createFrameWithFrameSetter:frameSetter config:config height:textHeight];
//將生成好的CTFrameRef實(shí)例和計(jì)算好的繪制高度保存到CoreTextData實(shí)例中祈匙,最后返回實(shí)例
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中進(jìn)行配置忽刽,代碼如下:
- (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 = @"這是一個(gè)測(cè)試代碼天揖,主要是為了展示富文本,前面一句話(huà)顯示紅色跪帝,第二句話(huà)顯示綠色今膊,三句話(huà)字體放大";
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;
}
最后運(yùn)行項(xiàng)目,查看效果伞剑,如下圖所示:
但是在實(shí)際開(kāi)發(fā)的時(shí)候斑唬,不會(huì)只是簡(jiǎn)簡(jiǎn)單單顯示這種,而且前后端數(shù)據(jù)交互啥的黎泣,我們拿到是這種的數(shù)據(jù)的話(huà)恕刘,處理起來(lái)很麻煩,所以一般都是和后臺(tái)進(jìn)行約定抒倚,規(guī)定一種格式褐着,方便前端進(jìn)行使用,最常見(jiàn)的就是JSON格式直接返回排版需要的配置衡便,例如:
[
{
"color":"red",
"size":"12",
"content":"但是在實(shí)際開(kāi)發(fā)的時(shí)候献起,不會(huì)只是簡(jiǎn)簡(jiǎn)單單顯示這種,而且前后端數(shù)據(jù)交互啥的",
"type":"txt"
},
{
"color":"green",
"size":"16",
"content":"我們拿到是這種的數(shù)據(jù)的話(huà)镣陕,處理起來(lái)很麻煩谴餐,所以一般都是和后臺(tái)進(jìn)行約定",
"type":"txt"
},
{
"color":"blue",
"size":"20",
"content":"規(guī)定一種格式,方便前端進(jìn)行使用呆抑,最常見(jiàn)的就是JSON格式直接返回排版需要的配置",
"type":"txt"
}
]
我以前做一個(gè)類(lèi)似微博廣場(chǎng)功能塊的圖文混排時(shí)候岂嗓,后臺(tái)返回的就是這種數(shù)據(jù)類(lèi)型,當(dāng)我們得到這樣的JSON數(shù)據(jù)后鹊碍,我們可以使用優(yōu)秀的三方庫(kù)進(jìn)行數(shù)據(jù)解析JSONKit厌殉,也可以通過(guò)系統(tǒng)提供的NSJSONSerialization進(jìn)行解析,得到NSDictionary對(duì)象的數(shù)據(jù)組后侈咕,通過(guò)以下一系類(lèi)方法進(jìn)行轉(zhuǎn)換得到我們繪制需要的數(shù)據(jù)公罕。
首先我們需要將接受到的JSON數(shù)據(jù)進(jìn)行解析,然后讀取JSON數(shù)據(jù)返回的配置以及內(nèi)容耀销,得到一個(gè)富文本楼眷,然后根據(jù)富文本去生成繪制模型,具體代碼如下:
#pragma mark --根據(jù)JSON數(shù)據(jù)熊尉,創(chuàng)建富文本
+ (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{
//創(chuàng)建一個(gè)可變路徑罐柳,圖形上下文中要繪制的圖形或線條的數(shù)學(xué)描述
CGMutablePathRef path = CGPathCreateMutable();
//追加矩形到可變路徑
CGPathAddRect(path, NULL, CGRectMake(0, 0, config.width, height));
//對(duì)核心文本框架對(duì)象的引用
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{
//創(chuàng)建一個(gè)可變字典,保存默認(rèn)富文本信息配置選項(xiàng)
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithDictionary:[self attributesWithConfig:config]];
//獲取數(shù)據(jù)中顏色值
UIColor *color = [self colorFromDictionary:dic];
if (color) {
//如果數(shù)據(jù)中給出顏色值狰住,替換默認(rèn)設(shè)置的色值
attributes[NSForegroundColorAttributeName] = color;
}
CGFloat fontSize = [dic[@"size"] floatValue];
if (fontSize > 0) {
//獲取數(shù)據(jù)返回的字體大小张吉,如果存在,創(chuàng)建一個(gè)CTFontRef實(shí)例催植,替換默認(rèn)設(shè)置項(xiàng)中的字體設(shè)置
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
attributes[NSFontAttributeName] = (__bridge id)fontRef;
//釋放CTFontRef實(shí)例
CFRelease(fontRef);
}
NSString *content = dic[@"content"];
//根據(jù)富文本培訓(xùn)選項(xiàng)以及給定字符串創(chuàng)建并返回一個(gè)富文本
return [[NSAttributedString alloc] initWithString:content attributes:attributes];
}
+ (NSAttributedString *)loadJSONContent:(NSString *)jsonContent config:(KGCTFrameParserConfig *)config{
//JSON字符串轉(zhuǎn)NSData
NSData *data = [jsonContent dataUsingEncoding:NSUTF8StringEncoding];
//創(chuàng)建一個(gè)可變富文本肮蛹,保存JSON數(shù)據(jù)
NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init];
//這里做下容錯(cuò)處理勺择,如果傳入的json數(shù)據(jù)是非空并且有內(nèi)容,進(jìn)行數(shù)據(jù)轉(zhuǎn)換
if (data) {
//因?yàn)镴SON數(shù)據(jù)本身最外層就是一個(gè)數(shù)組蔗崎,所以這塊需要用數(shù)組去接受JSON解析后得到的值
NSArray *arr = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
//這里再做下容錯(cuò)處理酵幕,如果是NSArray類(lèi),那就說(shuō)明數(shù)據(jù)沒(méi)有問(wèn)題缓苛,可以進(jìn)行下一步操作
if ([arr isKindOfClass:[NSArray class]]) {
//直接使用for in遍歷
for (NSDictionary *dict in arr) {
NSString *type = dict[@"type"];
//判斷type類(lèi)型是否是txt芳撒,因?yàn)楝F(xiàn)在只是對(duì)txt類(lèi)型進(jìn)行的處理,所以這塊需要進(jìn)行過(guò)濾
if ([type isEqualToString:@"txt"]) {
//獲取到單條數(shù)據(jù)富文本信息
NSAttributedString *as = [self attributedingWithDictionary:dict config:config];
//將單條數(shù)據(jù)富文本信息拼接到總數(shù)據(jù)中
[result appendAttributedString:as];
}
}
}
}
return result;
}
+ (KGCoreTextData *)parseJSONContent:(NSString *)jsonContent config:(KGCTFrameParserConfig *)config{
//通過(guò)JSON數(shù)據(jù)未桥,得到富文本
NSAttributedString *content = [self loadJSONContent:jsonContent config:config];
//創(chuàng)建CTFramesetterRef實(shí)例
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)content);
//設(shè)置繪制邊界大小
CGSize restrictSize = CGSizeMake(config.width, CGFLOAT_MAX);
//獲得實(shí)際繪制所需要的大小
CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, restrictSize, nil);
//因?yàn)閷挾纫呀?jīng)國(guó)定笔刹,所以在這獲取實(shí)際繪制需要的高度
CGFloat textHeight = coreTextSize.height;
//生成CTFrameRef實(shí)例
CTFrameRef frame = [self createFrameWithFrameSetter:frameSetter config:config height:textHeight];
//將生成好的CTFrameRef實(shí)例和計(jì)算好的繪制高度保存到CoreTextData實(shí)例中,最后返回實(shí)例
KGCoreTextData *data = [[KGCoreTextData alloc] init];
//設(shè)置實(shí)際繪制需要的實(shí)例對(duì)象
data.ctFrame = frame;
//設(shè)置實(shí)際繪制需要的高度
data.height = textHeight;
//因?yàn)榈讓訋?kù)不受ARC約束冬耿,所以需要手動(dòng)調(diào)用CFRelease來(lái)進(jìn)行釋放
//釋放生成的CTFrameRef實(shí)例
CFRelease(frame);
//釋放CTFramesetterRef實(shí)例
CFRelease(frameSetter);
//返回需要的繪制數(shù)據(jù)模型
return data;
}
方法里面的注釋寫(xiě)的很清晰舌菜,所以不做多的介紹。我們?cè)贙GDisPlayView內(nèi)部的設(shè)置還是不變亦镶,只需要在ViewController中對(duì)KGCTFrameParser進(jìn)行配置日月,代碼如下:
#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":@"但是在實(shí)際開(kāi)發(fā)的時(shí)候,不會(huì)只是簡(jiǎn)簡(jiǎn)單單顯示這種缤骨,而且前后端數(shù)據(jù)交互啥的爱咬,",
@"size":@"12",
@"color":@"red"
},
@{
@"type":@"txt",
@"content":@"我們拿到是這種的數(shù)據(jù)的話(huà),處理起來(lái)很麻煩绊起,所以一般都是和后臺(tái)進(jìn)行約定精拟,",
@"size":@"16",
@"color":@"green"
},
@{
@"type":@"txt",
@"content":@"規(guī)定一種格式,方便前端進(jìn)行使用虱歪,最常見(jiàn)的就是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
最后得到的效果如下圖所示:
到此我們的框架可以支持富文本格式的文本渲染了蜂绎,下一篇,開(kāi)始探索圖文混排笋鄙。
系列文章:
<a >《CoreText的簡(jiǎn)單使用(一)》</a>
<a >《CoreText的簡(jiǎn)單使用(二)》</a>
<a >《CoreText的簡(jiǎn)單使用(三)》</a>
<a >《CoreText的簡(jiǎn)單使用(四)》</a>
<a >《CoreText的簡(jiǎn)單使用(五)》</a>