longxdragon's blog

CoreText笔记 Part 1

CoreText是一种偏底层的技术,主要处理一些排版功能,比如处理字形、字体、行间距等等。说CoreText偏底层,是因为它其实就是Apple基于更底层库Quartz的一层封装,用起来更方便而已。由于其与Quartz直接打交道,所以性能也比上层的UIKit更高效。

这里借用TextKit的架构图来说明下CoreText的架构

注:TextKit是在iOS7之后才有的。

CoreText的应用场景很多,特别是一些对文字展示要求特别高的App。比如阅读器、新闻内容页展示、社交软件信息页等等,所以掌握CoreText很有必要。

下面我们自定义了一个View,并重写drawRect方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
// 1
CGContextRef context = UIGraphicsGetCurrentContext();
// 2
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// 3
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
// 4
NSAttributedString *attString = [[NSAttributedString alloc] initWithString:@"Hello CoreText!"];
// 5
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attString length]), path, NULL);
// 6
CTFrameDraw(frame, context);
CFRelease(frame);
CFRelease(framesetter);
CFRelease(path);
}

1、获得当前绘制的上下文;
2、由于Quartz库中是以左下角为(0,0)原点坐标,而CoreText的原点坐标是右上角,那么就需要上下翻转一下。
3、设置绘制的区域,本例中是矩形,当然你也可以尝试其他设置,比如:CGPathAddArc()方法设置绘制区域为圆形。
4、创建NSAttributedStringNSAttributedString就是CoreText的数据源,所有要绘制的格式都是在NSAttributedString里面设置,比如字体颜色、字形、段落、行距等等。
5、把NSAttributedString转化成CTFramesetterRef,再通过CTFramesetterRef创建CTFrameRef,CoreText的核心就是通过CTFrameRef绘制。
6、绘制。

下面介绍一些常用的属性:

1
2
3
4
5
6
kCTFontAttributeName //字体 CTFontRef
kCTForegroundColorFromContextAttributeName //背景颜色 CGColorRef
kCTForegroundColorAttributeName //字体颜色 CGColorRef
kCTParagraphStyleAttributeName //段落格式 CTParagraphStyleRef
kCTUnderlineStyleAttributeName //下划线宽度 CFNumberRef
kCTUnderlineColorAttributeName //下划线颜色 CGColorRef

所有属性中,比较复杂的当属kCTParagraphStyleAttributeName,它主要有如下样式设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
kCTParagraphStyleSpecifierAlignment = 0, //对齐属性
kCTParagraphStyleSpecifierFirstLineHeadIndent = 1, //首行缩进
kCTParagraphStyleSpecifierHeadIndent = 2, //段头缩进
kCTParagraphStyleSpecifierTailIndent = 3, //段尾缩进
kCTParagraphStyleSpecifierTabStops = 4, //制表符模式
kCTParagraphStyleSpecifierDefaultTabInterval = 5, //默认tab间隔
kCTParagraphStyleSpecifierLineBreakMode = 6, //换行模式
kCTParagraphStyleSpecifierLineHeightMultiple = 7, //多行高
kCTParagraphStyleSpecifierMaximumLineHeight = 8, //最大行高
kCTParagraphStyleSpecifierMinimumLineHeight = 9, //最小行高
kCTParagraphStyleSpecifierLineSpacing = 10, //行距
kCTParagraphStyleSpecifierParagraphSpacing = 11, //段落间距 在段的未尾(Bottom)加上间隔,这个值为负数。
kCTParagraphStyleSpecifierParagraphSpacingBefore = 12, //段落前间距 在一个段落的前面加上间隔。TOP
kCTParagraphStyleSpecifierBaseWritingDirection = 13, //基本书写方向
kCTParagraphStyleSpecifierMaximumLineSpacing = 14, //最大行距
kCTParagraphStyleSpecifierMinimumLineSpacing = 15, //最小行距
kCTParagraphStyleSpecifierLineSpacingAdjustment = 16, //行距调整
kCTParagraphStyleSpecifierCount = 17, //

除了上面复杂的属性以后,是不是发现,使用CoreText绘制内容其实很简单?!记住上面6步就可以了!

但我们都知道,实际在项目中使用的时候,我们不可能这么写,为了能更方便的使用,我们可能需要稍微封装一下。
在.h文件中添加一些常设置的属性(这里只举个例):

1
2
3
4
5
@interface CTDisplayLabel : UILabel
@property (nonatomic, assign) CGFloat lineSpace;
@end

记得在初始化时,给它们赋上初始值。

然后在创建NSAttributedString后,添加属性值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//设置字体和大小
CTFontRef helveticaBold = CTFontCreateWithName((CFStringRef)self.font.fontName,self.font.pointSize,NULL);
[attString addAttribute:(id)kCTFontAttributeName value:(__bridge id)helveticaBold range:NSMakeRange(0,[attString length])];
//设置字体颜色
[attString addAttribute:(id)kCTForegroundColorAttributeName value:(id)(self.textColor.CGColor) range:NSMakeRange(0,[attString length])];
//换行
CTLineBreakMode lineBreak = kCTLineBreakByTruncatingTail;
CTParagraphStyleSetting lineBreakMode;
lineBreakMode.spec = kCTParagraphStyleSpecifierLineBreakMode;
lineBreakMode.value = &lineBreak;
lineBreakMode.valueSize = sizeof(CTLineBreakMode);
//行间距
CTParagraphStyleSetting lineSpaceSetting;
lineSpaceSetting.spec = kCTParagraphStyleSpecifierLineSpacing;
lineSpaceSetting.value = &_lineSpace;
lineSpaceSetting.valueSize = sizeof(float);
CTParagraphStyleSetting settings[] = {lineBreakMode,lineSpaceSetting};
CTParagraphStyleRef style = CTParagraphStyleCreate(settings , sizeof(settings));
[attString addAttribute:(id)kCTParagraphStyleAttributeName value:(__bridge id)style range:NSMakeRange(0,[attString length])];

##深入思考
如果我们要在cell中使用到CTDisplayLabel时,由于需要先计算cell的高度,这时候我们就希望,不要设置CTDisplayLabel的内容(至少是不需要绘制!),就可以知道此控件适配的高度。要求感觉很难,但Apple已经为我们封装好了方法:CTFramesetterSuggestFrameSizeWithConstraints,具体实现如下:

1
2
3
4
5
6
7
8
- (CGSize)sizeThatFitsaWithText:(NSString *)text width:(CGFloat)width {
NSAttributedString *attString = [self attributedTextWithConfig:self.text];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
CGSize constraints = CGSizeMake(width, CGFLOAT_MAX);
CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, 0), nil, constraints, nil);
return CGSizeMake(width, coreTextSize.height);
}

这样就可以轻松的计算适配内容的高度了!!

参考:
《iOS进阶开发》 – 唐巧
https://github.com/TTTAttributedLabel/TTTAttributedLabel