CoreText原理及基本使用方法

jopen 8年前發布 | 13K 次閱讀 CoreText iOS開發 移動開發

關于富文本的排版也是現在的一個技術點,以下是近日關于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:@"\tWhen I will learn CoreText, i think it will hard for me.But it is easy.\n\tIn 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:@"\tWhen I will learn CoreText, i think it will hard for me.But it is easy.\n\tIn 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中有任何錯誤歡迎指正,謝謝!

來自: http://www.cnblogs.com/purple-sweet-pottoes/p/5109413.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!