CoreText高级应用——富文本、按钮、输入框的混编

关于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绘制富文本

  1. 填充背景色
  2. 翻转坐标系
  3. 创建绘制区域
  4. 绘制文本

    //获取当前上下文
    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.其他注意事项

  1. 输入框和按钮只需要在页面第一次加载的时候创建,要避免重复创建
  2. 新建的输入框和按钮需要传递给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);
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值