zoukankan      html  css  js  c++  java
  • CoreText原理及基本使用方法

      关于富文本的排版也是现在的一个技术点,以下是近日关于CoreText的学习记录以及个人理解,希望能对正在学习CoreText的朋友起到帮助。

      1.框架坐标系

      首先让我们先来看看CoreText坐标系UIKit坐标系的不同
      

      从图中可看出CoreText坐标系是以左下角为坐标原点,而我们常使用的UIKit是以左上角为坐标原点,因此在CoreText中的布局完成后需要对其坐标系进行转换,否则直接绘制出现位置反转的镜像情况。在通常情况下我们一般做法是直接获取当前上下文。并将当前上下文的坐标系转换为CoreText坐标系,再将布局好的CoreText绘制到当前上下文中即可。以下是此种方案的实现逻辑

        //获取当前上下文
        CGContextRef context = UIGraphicsGetCurrentContext();
        //翻转坐标系步骤
        //设置当前文本矩阵
        CGContextSetTextMatrix(context, CGAffineTransformIdentity);
        //文本沿y轴移动
        CGContextTranslateCTM(context, 0, self.bounds.size.height);
        //文本翻转成为CoreText坐标系
        CGContextScaleCTM(context, 1, -1);

      2.CoreText文本布局

      CoreText的布局同UIKit布局不太相同,CoreText中布局大体思路是确定文本绘制区域,接着得到文本实际大小(frame)。其具体步骤如下:

      1.首先要确定布局时绘制的区域,其对应的类为CG(Mutable)PathRef

       2.设置文本内容,其对应的类为NS(Mutable)AttributedString

       3.根据文本内容配置其CTFramesetterRef

       4.利用CTFramesetterRef得到CTFrame

      有了以上具体步骤那我们开始实际的代码操作:

        //1.创建绘制区域,显示的区域可以用CGMUtablePathRef生成任意的形状
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathAddRect(path, NULL, CGRectMake(20, 50, self.bounds.size.width - 40, self.bounds.size.height - 100));   
        //2.创建需要绘制的文字
        NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:@"	When I will learn CoreText, i think it will hard for me.But it is easy.
    	In fact,if you bengin learn, you can know that every thing is easy when you start.you just need some knowlages"];
        //3.根据AttString生成CTFramesetterRef
        CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
        CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, [attString length]), path, NULL);

      2.1文本属性设置

      此处我们使用的是NSmutableAttributedString来进行文本设置,是因为我们可以很方便的设置其属性,以下为部分属性设置

        //设置绘制的文本内容
         NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:@"	When I will learn CoreText, i think it will hard for me.But it is easy.
    	In fact,if you bengin learn, you can know that every thing is easy when you start.you just need some knowlages"];
        //设置文本内容的属性
        //1设置部分文字颜色
        [attString addAttribute:(id)kCTForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0 , 27)];
        //2设置部分文字字体
        CGFloat fontSize = 20;
        CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
        [attString addAttribute:(id)kCTFontAttributeName value:(__bridge id)fontRef range:NSMakeRange(0, 27)];
        //3设置斜体
        CTFontRef italicFontRef = CTFontCreateWithName((CFStringRef)[UIFont italicSystemFontOfSize:20].fontName, 16, NULL);
        [attString addAttribute:(id)kCTFontAttributeName value:(__bridge id)italicFontRef range:NSMakeRange(27, 9)];
        //4设置下划线
        [attString addAttribute:(id)kCTUnderlineStyleAttributeName value:(id)[NSNumber numberWithInteger:kCTUnderlineStyleDouble] range:NSMakeRange(36, 10)];
        //5设置下划线颜色
        [attString addAttribute:(id)kCTUnderlineColorAttributeName value:(id)[UIColor greenColor].CGColor range:NSMakeRange(36, 10)];
        //6设置空心字
        long number1 = 2;
        CFNumberRef numRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &number1);
        [attString addAttribute:(id)kCTStrokeWidthAttributeName value:(__bridge id)numRef range:NSMakeRange(56, 10)];
        //7设置字体间距
        long number = 10;
        CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt8Type, &number);
        [attString addAttribute:(id)kCTKernAttributeName value:(__bridge id)num range:NSMakeRange(40, 10)];
        //8设置行间距
        CGFloat lineSpacing = 10;
        const CFIndex kNumberOfSettings = 3;
        CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
            {kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing},
            {kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &lineSpacing},
            {kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &lineSpacing}
        };
        CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);
        [attString addAttribute:(id)kCTParagraphStyleAttributeName value:(__bridge id)theParagraphRef range:NSMakeRange(0, [attString length])];

      2.2图片文本内容

      图片宽高在工程中都需要加载后才知道,而在文本绘制中需要直接留出其位置再进行绘制,所以图片的宽高都是在数据中保存好的,此处笔者用固定值来表示其宽高。为了留出其位置我们需要用空白的字符来做占位符使用。为了知道其图片绘制的位置(即空白占位符位置)我们需要设置代理才能够得知图片绘制位置。具体步骤如下:

      1.创建CTRunDelegateCallbacks 回调函数:通过回调函数来确定图片绘制的宽高

       2.创建空白占位字符  

       3.设置CTRunDeleagte:通过代理来找到该字符串,并确定图片绘制的原点

      下面让我们来看看具体的实现代码

    #pragma mark - CTRunDelegateCallbacks Method
    //此处使用的字典结构来存储数值
    static CGFloat heightCallBack(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];
    }
    
    #pragma mark - 空白占位符及代理设置
        //CTRunDelegateCallBacks:用于保存指针的结构体,由CTRun delegate进行回调
        CTRunDelegateCallbacks callbacks;
        memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));
        callbacks.version = kCTRunDelegateVersion1;
        callbacks.getAscent = heightCallBack;
        callbacks.getDescent = descentCallBack;
        callbacks.getWidth = widthCallBack;
        //图片信息字典
        NSDictionary *imgInfoDic = @{@"width":@320,@"height":@230};
        //创建CTRunDelegate的代理
        CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void*)imgInfoDic);
        //使用oxFFFC作为空白占位符
        unichar objectReplacementChar = 0xFFFC;
        NSString *content = [NSString stringWithCharacters:&objectReplacementChar length:1];
        NSMutableAttributedString *space = [[NSMutableAttributedString alloc] initWithString:content];
        //设置代理
       CFAttributedStringSetAttribute((CFMutableAttributedStringRef)space, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);

      在贴出获取图片位置代码前,还需要补充一个理论知识,在CoreText中所有的布局都是基于行(CTLineRef)来进行的,每行都是一个CTLineRef对象,在每行当中又包含多个属性(CTRunRef)每行的属性可设置代理,如上面笔者就是对空白占位符这个CTRunRef设置了代理。下面为CTLineRef和CTRunRef的示意图

      

      明白此中原理后便可以上代码了解具体怎么实现

        //获取CTLine数组
        NSArray *lines = (NSArray *)CTFrameGetLines(ctframe);
        NSInteger lineCount = lines.count;
        CGPoint lineOrigins[lineCount];
        CTFrameGetLineOrigins(ctframe, CFRangeMake(0, 0), lineOrigins);
        //遍历每一个CTline
        for (NSInteger i = 0; i < lineCount; i ++) {
            CTLineRef line = (__bridge CTLineRef)lines[i];
            NSArray *runObjArray = (NSArray *)CTLineGetGlyphRuns(line);
            //遍历每个CTLine中的CTRun找到空白字符的delegate
            for (id runObj in runObjArray) {
                CTRunRef run = (__bridge CTRunRef)runObj;
                NSDictionary *runAttributes = (NSDictionary *)CTRunGetAttributes(run);
                CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(id)kCTRunDelegateAttributeName];
                if (delegate == nil) {
                    continue;
                }
                NSDictionary *metaDic = CTRunDelegateGetRefCon(delegate);
                if (![metaDic isKindOfClass:[NSDictionary class]]) {
                    continue;
                }
                //找到代理后开始计算空白字符的位置
                CGRect runBounds;
                CGFloat ascent;
                CGFloat descent;
                
                runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
                runBounds.size.height = ascent + descent;
                //计算在行当中的x偏移量
                CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
                runBounds.origin.x = lineOrigins[i].x + xOffset;
                runBounds.origin.y = lineOrigins[i].y - descent;
                //获得ctframe的绘制区域
                CGPathRef pathRef = CTFrameGetPath(ctframe);
                //计算此绘制区域的范围
                CGRect colRect = CGPathGetBoundingBox(pathRef);
                //计算在此区域中空白字符的位置
                CGRect delegateBounds= CGRectOffset(runBounds, colRect.origin.x, colRect.origin.y);
                //记录空白字符位置
                _imageRect = delegateBounds;
                //返回空白字符位置
                return delegateBounds;
            }
        }
    //若没有找到对应的代理则返回空位置
    return CGRectZero;

      3.绘制文本内容

      绘制文本内容相对来说就比较简单了,只需要在2句代码即可搞定

        //绘制文本
        CTFrameDraw(frame, context);
        //绘制图像
        UIImage *image = [UIImage imageNamed:@"boat.jpg"];
        CGContextDrawImage(context, _imageRect, image.CGImage);

      4.总结

      到此一个基本的CoreText布局排版已完成(注意绘制文本需要在drawRect中进行)。这里放上一个demo链接https://github.com/PurpleSweetPotatoes/CoreText_Learn.git,demo中包含了富文本点击事件的处理,是对《iOS开发进阶》书中CoreText的示例的整理,其中的逻辑思路就不在此赘述了,在demo中有详细的注释,朋友们可以直接下载学习。若文章或demo中有任何错误欢迎指正,谢谢!

      

  • 相关阅读:
    个人日志-6.27
    <软件工程>课程总结
    团队项目--地铁信息查询-UML图初步设计
    7-4 日报
    7-5小组日报 最终版的发布
    7-1 7-3
    软工日报6-30
    软工日报 6-29
    6-28小组会议记录
    6-27小组讨论内容
  • 原文地址:https://www.cnblogs.com/purple-sweet-pottoes/p/5109413.html
Copyright © 2011-2022 走看看