上篇展示了CoreText
排版的基础能力,但是制作一个排版引擎需要进一步封装,根据设计模式的“单一功能”原则应该把不同功能分给不同类中处理。如下:
1.一个显示用的类,仅负责内容显示,不负责排版;
2.一个模型类,用于承载显示做需要的所有数据;
3.一个排版类,用于实现文字内容的排版;
4.一个配置类,用于实现排版是的可配置项。
根据以上原则,我们拆分出了4个类:
1.CTFrameParserConfig
,用于配置绘制参数,比如:字体大小、字体颜色、行间距等等;
2.CTFrameParser
,用于生成最后需要绘制的CTFrameRef
实例;
3.CoreTextData
,用于CTFrameParser
生成的CTFrameRef
实例,以及CTFrameRef
实际绘制需要的高度。
4.CTDisplayView
,持有CoreTextData
类的实例,负责讲CTFrameRef
绘制到界面上。具体代码实现如下:
CTFrameParserConfig 类:
#import
@interface CTFrameParserConfig : NSObject
@property (nonatomic, assign) CGFloat width;
@property (nonatomic, assign) CGFloat fontSize;
@property (nonatomic, assign) CGFloat lineSpace;
@property (nonatomic, strong) UIColor *textColor;
@end
#import "CTFrameParserConfig.h"
@implementation CTFrameParserConfig
- (instancetype)init {
self = [super init];
if (self) {
_width = 200.0f;
_fontSize = 16.0f;
_lineSpace = 8.0f;
_textColor = RGB(108, 108, 108);
}
return self;
}
@end
CTFrameParser 类:
#import
#import "CoreTextData.h"
#import "CTFrameParserConfig.h"
@interface CTFrameParser : NSObject
+ (CoreTextData *)parserContent:(NSString *)content config:(CTFrameParserConfig *)config;
@end
+ (CoreTextData *)parserContent:(NSString *)content config:(CTFrameParserConfig *)config {
NSDictionary *attributes = [self attributesWithConfig:config];
NSAttributedString *contentString = [[NSAttributedString alloc] initWithString:content attributes:attributes];
// 创建CTFramesetterRef实例
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)contentString);
// 获得绘制区域高度
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实例中,最后返回CoreTextData实例
CoreTextData *data = [[CoreTextData alloc] init];
data.ctFrame = frame;
data.height = textHeight;
// 释放内存
CFRelease(frame);
CFRelease(framesetter);
return data;
}
+ (NSDictionary *)attributesWithConfig:(CTFrameParserConfig *)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[(id)kCTForegroundColorAttributeName] = (id)textColor.CGColor;
dict[(id)kCTFontAttributeName] = (__bridge id)fontRef;
dict[(id)kCTParagraphStyleAttributeName] = (__bridge id)theParagraphRef;
CFRelease(theParagraphRef);
CFRelease(fontRef);
return dict;
}
+ (CTFrameRef)createFrameWithFramesetter:(CTFramesetterRef)framesetter config:(CTFrameParserConfig *)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
CoreTextData 类:
#import
@interface CoreTextData : NSObject
@property (nonatomic, assign) CTFrameRef ctFrame;
@property (nonatomic, assign) CGFloat height;
@end
#import "CoreTextData.h"
@implementation CoreTextData
- (void)setCtFrame:(CTFrameRef)ctFrame {
if (_ctFrame != ctFrame) {
if (_ctFrame != nil) {
CFRelease(_ctFrame);
}
CFRetain(ctFrame);
_ctFrame = ctFrame;
}
}
- (void)dealloc {
if (_ctFrame != nil) {
CFRelease(_ctFrame);
_ctFrame = nil;
}
}
@end
CTDisplayView 类:
#import
#import "CoreTextData.h"
@interface CTDisplayView : UIView
@property (nonatomic, strong) CoreTextData *data;
@end
#import "CTDisplayView.h"
@implementation CTDisplayView
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
if (self.data) {
CTFrameDraw(self.data.ctFrame, context);
}
}
最终在 ViewController 类中的实现:
#import "ViewController.h"
#import "CTDisplayView.h"
#import "CTFrameParser.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet CTDisplayView *displayView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CTFrameParserConfig *config = [[CTFrameParserConfig alloc] init];
config.textColor = [UIColor redColor];
config.width = self.displayView.width;
CoreTextData *data = [CTFrameParser parserContent:@"小说阅读器,开始...." config:config];
self.displayView.data = data;
self.displayView.height = data.height;
self.displayView.backgroundColor = [UIColor yellowColor];
}
@end
我们通过UML来看一下各个类之间的关系:
总结:(关键类:CTFrameParser
)
如此可以看出来CTFrameParser
通过CTFrameParserConfig
和CoreTextData
来完成排版工作,使得排版的工作从CTDisplayView
类中完全解放出来,让其只负责最后的绘制和显示。