参考资料:
1.http://www.allenchiang.com/2014/05/28/core-text/
2.唐巧:《iOS进阶》
一、iOS开发中的文字排版
通常我们使用UILabel、UITextField、UITextView在iOS上展示一些我们需要的文字。前者用于简单的展示,后两者可以用于接受用户的输入。通常情况下我们用上述3者展示简单的纯文本,如果我们需要展示图文混排或者稍微带一点排版样式的文字时,我们需要使用更底层的一些技术,比如Text Kit 或者 Core Text。
上图展示的iOS7的框架层次结构,可以看到基于Core Text的Text Kit为上述3个常用控件提供了技术支持
二、iOS中的CoreText介绍
苹果官方文档的解释:
Core Text is an advanced, low-level technology for laying out text and handling fonts. Core Text works directly with Core Graphics (CG), also known as Quartz, which is the high-speed graphics rendering engine that handles two-dimensional imaging at the lowest level in OS X and iOS.
下面我们跟着这段解释详细了解一下其中的关键字:
①low-level technology:
它直接和底层的CoreGraphics联系,表明它也是一种底层的技术。从中也可以了解它的实现是通过C语言的
②laying out text and handling fonts:
它是一种处理字符的布局引擎(当然,不仅仅是字符),通过这个布局引擎,我们可以实现图文混排、文字链接等UILabel、UITextField控件无法实现的功能
三、CoreText布局引擎结构
首先看一幅图(官方文档中的图):
我们可以这么理解字符如何绘制到屏幕上:
①:首先创建一个属性字符串CFAttributedStringRef,它包括了字符的颜色、大小、字体等信息
②:然后创建CTFramesetter,它包括了字符的行间距、缩进、断行方式等
生成的CTFramesetter同时也会唤起一个CTTypesetter,它的作用是将CFAttributedStringRef中的字符转换成对应的字形
③:由①②我们可以生成一个CTFrame,可以想象成一个字符区域。(我们添加一个视图的时候,要设置视图的frame。同理添加字符的时候,也要设置一个frame)
CTFrame中包含CTLine、CTRun。CTLine即每一行,而CTRun是指一行中连续的一段包含同样属性和方向的文字。CTLine中可以有多个CTRun
四、重定制CTRun
通常情况下图文混排,包含了很多内容,还是需要开发者自己做一些事情,对于每个CTRun,我们可以自己设置它的属性。
1.首先,我们看看一个字形是如何定义的:
我们要注意的有3个:
①Bounding
②Ascent
③Descent
2.那么图文混排中的文字和颜色到底是如何绘制上去的呢?
①如果我们要绘制文字: 通过以下方法直接绘制CTFrame
void CTFrameDraw(
CTFrameRef frame,
CGContextRef context );
②如果我们要绘制图片:
其实只是告诉Core Text有一个地方需要占多大的位置,这样系统就会在指定的地方给出了足够的空间。真正的图像绘制其实还是需要我们自己通过Core Graphic来做。
实际操作中,可以:创建空白占位符,并且设置它的CTRunDelegate信息(通过信息判断图片需要的空间大小)
例如:
static CGFloat ascentCallback(void *ref){
return [(NSNumber*)[(__bridge NSDictionary*)ref objectForKey:@"height"] floatValue];
}
static CGFloat descentCallback(void *ref){
return 0;
}
static CGFloat widthCallback(void* ref){
return [(NSNumber*)[(__bridge NSDictionary*)ref objectForKey:@"width"] floatValue];
}
+ (NSAttributedString *)parseImageDataFromNSDictionary:(NSDictionary *)dict
config:(CTFrameParserConfig*)config {
CTRunDelegateCallbacks callbacks;
memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));
callbacks.version = kCTRunDelegateVersion1;
callbacks.getAscent = ascentCallback;
callbacks.getDescent = descentCallback;
callbacks.getWidth = widthCallback;
CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)(dict));
// 使用0xFFFC作为空白的占位符
unichar objectReplacementChar = 0xFFFC;
NSString * content = [NSString stringWithCharacters:&objectReplacementChar length:1];
NSDictionary * attributes = [self attributesWithConfig:config];
NSMutableAttributedString * space = [[NSMutableAttributedString alloc] initWithString:content
attributes:attributes];
CFAttributedStringSetAttribute((CFMutableAttributedStringRef)space, CFRangeMake(0, 1),
kCTRunDelegateAttributeName, delegate);
CFRelease(delegate);
return space;
}