关于CoreText基础,推崇紫色大番薯的《CoreText原理及基本使用方法》一文,有珠玉在前,我就不再详述。
CoreText是用于处理文字和字体的底层技术,多用于富文本排版布局。IOS6.0以后UILabel、UITextField增加了新的富文本处理方式,使用起来要比CoreText友好的多。现在网上很多CoreText应用实例,都可以使用UILabel、UITextField更加简便的给予实现。但是总有一些复杂的问题必须用到CoreText,这里分享一个我遇到过实例。
需解决的问题:
在view中自动排版展示文档格式储存的试卷,文档的首行为标题,填空题需要直接在题目中填写答案, 选择题的选项以按钮的形式展现。
原始文件实例
ETHNOGRAPHY IN BUSINESS
1. It can be used in business:
• to investigate customer needs and ……………………
• to help …………………… develop new designs
2. What change in the road network is known to have benefited the town most?
○ the construction of a bypass
○ the development of cycle paths
○ the banning of cars from certain streets
最终显示效果
解决问题的思路:
1. 把文档转换为富文本
2. 使用CoreText绘制富文本
3. 确定输入框,选项位置
4. 创建UITextField作为输入框,创建UIButton作为选项,
1. 文档转换为富文本
以下信息要加入到富文本之中:
- 行间距、段间距等格式信息
- 标题样式
- 输入框和按钮的标记
-(NSMutableAttributedString *)getMutableAttributedString{
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:_text];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineSpacing:5];
[paragraphStyle setParagraphSpacingBefore:8];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [_text length])];
//第一行为标题
NSRange firstLineRange=[_text lineRangeForRange:NSMakeRange(0, 1)];
[attributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:20] range:firstLineRange]; // 6.0+
//………………处添加UITextView作为输入框
NSRange inputRange=[_text rangeOfString:@"………………"];
while(inputRange.length>0){
[attributedString addAttribute:(id)@"FTCoreTextDataName" value:(id)@"input" range:inputRange];
if (inputRange.length+inputRange.location==_text.length) {
inputRange=NSMakeRange(0,0);
} else {
inputRange=[_text rangeOfString:@"………………" options:0 range:NSMakeRange(inputRange.location+1,_text.length-inputRange.length-inputRange.location-1)];
}
}
//○开始行为按钮,
NSRange buttonRange=[_text rangeOfString:@"○"];
while(buttonRange.length>0){
[attributedString addAttribute:(id)@"FTCoreTextDataName" value:(id)@"button" range:buttonRange];
buttonRange=[_text rangeOfString:@"○" options:0 range:NSMakeRange(buttonRange.location+1,_text.length-buttonRange.length-buttonRange.location-1)];
}
return attributedString;
}
2. 使用CoreText绘制富文本
- 填充背景色
- 翻转坐标系
- 创建绘制区域
- 绘制文本
//获取当前上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//填充背景色
[self.backgroundColor setFill];
CGContextFillRect(context, rect);
//翻转坐标系步骤
//设置当前文本矩阵
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
//文本沿y轴移动
CGContextTranslateCTM(context, 0, self.bounds.size.height);
//文本翻转成为CoreText坐标系
CGContextScaleCTM(context, 1.0, -1.0);
//获取NSMutableAttributedString
NSMutableAttributedString *attributedString=[self getMutableAttributedString];
//根据AttString生成CTFramesetterRef
CTFramesetterRef ctFramesetter = CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef)attributedString);
//创建绘制区域
CGMutablePathRef path = CGPathCreateMutable();
CGRect bounds = CGRectMake(0.0, 0.0, self.bounds.size.width, self.bounds.size.height);
CGPathAddRect(path, NULL, bounds);
//绘制文本
CTFrameRef ctFrame = CTFramesetterCreateFrame(ctFramesetter,CFRangeMake(0, 0), path, NULL);
CTFrameDraw(ctFrame, context);
3.确定输入框,选项位置
遍历每一个CTRunRef,查找我们在富文本中加入的标记
for (int i = 0; i < CFArrayGetCount(lines); i++) {
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGFloat lineAscent;
CGFloat lineDescent;
CGFloat lineLeading;
CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
CFArrayRef runs = CTLineGetGlyphRuns(line);
//遍历每一个CTRunRef
for (int j = 0; j < CFArrayGetCount(runs); j++) {
CGPoint lineOrigin = lineOrigins[i];
CTRunRef run = CFArrayGetValueAtIndex(runs, j);
NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);
const CGPoint *point=CTRunGetPositionsPtr(run);
NSString *attributeName = [attributes objectForKey:@"FTCoreTextDataName"];
if ([attributeName isEqualToString:@"input"]) {
//输入框标记
。。。。。。
}else if ([attributeName isEqualToString:@"button"]){
//按钮标记
。。。。。。 }
}
}
4.创建输入框及选项按钮
此处需注意CoreText坐标系和UIKit坐标系的换算,新建控件使用的是UIKit坐标系。
创建输入框
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(point[0].x+7, self.frame.size.height-lineOrigin.y-14, 150.0f, 20.0f)];
[textField setBorderStyle:UITextBorderStyleNone]; //外框类型
textField.placeholder = @""; //默认显示的字
textField.font=[UIFont fontWithName:@"TimesNewRomanPSMT" size:12.f];
textField.textColor=[UIColor blueColor];
[self addSubview:textField];
创建按钮
UIButton *button =[[UIButton alloc]initWithFrame:CGRectMake(point[0].x-5, self.frame.size.height-lineOrigin.y-16, 250, 22.0f)];
UIColor *ucolor=[UIColor lightGrayColor];
[button.layer setMasksToBounds:YES];
[button.layer setCornerRadius:7.0]; //设置矩圆角半径
[button.layer setBorderWidth:1.0]; //边框宽度
[button.layer setBorderColor:ucolor.CGColor];//边框颜色
[self addSubview:button];
// 为UIButton关联响应事件
[self.delegate addCoreTextViewObject:button type:@"button"];
5.其他注意事项
- 输入框和按钮只需要在页面第一次加载的时候创建,要避免重复创建。
- 新建的输入框和按钮需要传递给ViewController,由ViewController来响应事件。
完整drawRect()代码
- (void)drawRect:(CGRect)rect {
//获取当前上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//填充背景色
[self.backgroundColor setFill];
CGContextFillRect(context, rect);
//翻转坐标系步骤
//设置当前文本矩阵
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
//文本沿y轴移动
CGContextTranslateCTM(context, 0, self.bounds.size.height);
//文本翻转成为CoreText坐标系
CGContextScaleCTM(context, 1.0, -1.0);
//获取NSMutableAttributedString
NSMutableAttributedString *attributedString=[self getMutableAttributedString];
//根据AttString生成CTFramesetterRef
CTFramesetterRef ctFramesetter = CTFramesetterCreateWithAttributedString((CFMutableAttributedStringRef)attributedString);
//创建绘制区域
CGMutablePathRef path = CGPathCreateMutable();
CGRect bounds = CGRectMake(0.0, 0.0, self.bounds.size.width, self.bounds.size.height);
CGPathAddRect(path, NULL, bounds);
//绘制文本
CTFrameRef ctFrame = CTFramesetterCreateFrame(ctFramesetter,CFRangeMake(0, 0), path, NULL);
CTFrameDraw(ctFrame, context);
//获取CTLine数组
CFArrayRef lines = CTFrameGetLines(ctFrame);
CGPoint lineOrigins[CFArrayGetCount(lines)];
CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, 0), lineOrigins);
//遍历每一个CTline
for (int i = 0; i < CFArrayGetCount(lines); i++) {
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGFloat lineAscent;
CGFloat lineDescent;
CGFloat lineLeading;
CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
CFArrayRef runs = CTLineGetGlyphRuns(line);
//遍历每一个CTRunRef
for (int j = 0; j < CFArrayGetCount(runs); j++) {
CGPoint lineOrigin = lineOrigins[i];
CTRunRef run = CFArrayGetValueAtIndex(runs, j);
NSDictionary* attributes = (NSDictionary*)CTRunGetAttributes(run);
const CGPoint *point=CTRunGetPositionsPtr(run);
NSString *attributeName = [attributes objectForKey:@"FTCoreTextDataName"];
if ([attributeName isEqualToString:@"input"]) {
//添加输入框
if (createObjectMode) {
UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(point[0].x+7, self.frame.size.height-lineOrigin.y-14, 150.0f, 20.0f)];
[textField setBorderStyle:UITextBorderStyleNone]; //外框类型
textField.placeholder = @""; //默认显示的字
textField.font=[UIFont fontWithName:@"TimesNewRomanPSMT" size:12.f];
textField.textColor=[UIColor blueColor];
[self addSubview:textField];
// 为UITextView关联响应事件
[self.delegate addCoreTextViewObject:textField type:@"input"];
}else{
CGRect frame=CGRectMake(point[0].x+7, self.frame.size.height-lineOrigin.y-14, 150.0f, 20.0f);
[self.delegate resetObjectFrame:frame type:@"input"];
}
}else if ([attributeName isEqualToString:@"button"]){
//添加按钮
if (createObjectMode) {
UIButton *button =[[UIButton alloc]initWithFrame:CGRectMake(point[0].x-5, self.frame.size.height-lineOrigin.y-16, 250, 22.0f)];
UIColor *ucolor=[UIColor lightGrayColor];
[button.layer setMasksToBounds:YES];
[button.layer setCornerRadius:7.0]; //设置矩圆角半径
[button.layer setBorderWidth:1.0]; //边框宽度
[button.layer setBorderColor:ucolor.CGColor];//边框颜色
[self addSubview:button];
// 为UIButton关联响应事件
[self.delegate addCoreTextViewObject:button type:@"button"];
}else{
CGRect frame=CGRectMake(point[0].x-5, self.frame.size.height-lineOrigin.y-16, 250, 22.0f);
[self.delegate resetObjectFrame:frame type:@"button"];
}
}
}
}
if (createObjectMode){
createObjectMode=NO;
}
CFRelease(ctFrame);
CFRelease(path);
CFRelease(ctFramesetter);
}