iOS-CoreText 的使用

CoreText是iOS/OSX中文本顯示的一個底層框架阎肝,它是用C語言寫成的渐北,有快速簡單的優(yōu)勢筛璧。iOS中的Textkit牛欢,webkit都是基于CoreText封裝的骡男。本文討論一下CoreText的一些常規(guī)用法,知道它有哪些功能傍睹,我們在開發(fā)中遇到OC的類解決不了的問題隔盛,或者需要提高性能的時候,可以使用它們拾稳。我喜歡用代碼說話吮炕,一步一步走:

先了解CoreText的機(jī)構(gòu)圖

coreText的機(jī)構(gòu)圖

普通段落文本的顯示

//1、初始化一個畫布

CGContextRef context = UIGraphicsGetCurrentContext();

//2访得、反轉(zhuǎn)畫布的坐標(biāo)系龙亲,由于OS中坐標(biāo)系原點在左下角,iOS中在左上角悍抑,所以在iOS需要反轉(zhuǎn)一下鳄炉,OS中不用。

CGContextTranslateCTM(context, 0, self.bounds.size.height);

CGContextScaleCTM(context, 1.0, -1.0);

//3搜骡、設(shè)置文本的矩陣

CGContextSetTextMatrix(context, CGAffineTransformIdentity);

//創(chuàng)建文本范圍的路徑

CGMutablePathRef ?path = CGPathCreateMutable();

//:創(chuàng)建一個矩形文本區(qū)域拂盯。

CGRect bouns = CGRectMake(10.0,10.0记靡,200.0谈竿,200.0);

CGPathAddRect(path,NULL,bounds);

完成了文本區(qū)域的設(shè)置团驱,開始準(zhǔn)備顯示的素材,這里就顯示一段文字空凸。

CFStringRef textString = CFSTR("Hello, World! I know nothing in the world that has as much power as a word. Sometimes I write one, and I look at it, until it begins to shine.");

//創(chuàng)建一個多屬性字段嚎花,maxlength為0;maxlength是提示系統(tǒng)有需要多少內(nèi)部空間需要保留劫恒,0表示不用提示限制贩幻。

CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);

//為attrString添加內(nèi)容,也可以用CFAttributedStringCreate 開頭的幾個方法两嘴,根據(jù)不同的需要和參數(shù)選擇合適的方法丛楚。這里用替換的方式,完成寫入憔辫。

CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0),textString);//此時attrString就有內(nèi)容了

//為多屬性字符串增添一個顏色的屬性趣些,這里就是奇妙之處,富文本的特點就在這里可以自由的調(diào)整文本的屬性:比如贰您,顏色坏平,大小,字體锦亦,下劃線舶替,斜體,字間距杠园,行間距等

CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();//創(chuàng)建一個顏色容器對象顾瞪,這里我們用RGB,當(dāng)然還有很多其他顏色容器對象抛蚁,可以根據(jù)需要和方便自由搭配陈醒,總有一款適合你的。

//創(chuàng)建一個紅色的對象

CGFloat components[] = {1.0,0.0,0.0,0.8};

CGColorRef red = CGColorCreat(rgbColorSpace,components)

CGColorSpaceRelease(rgbColorSpace);//不用的對象要及時回收哦

//給前12個字符增添紅色屬性

CFAttributedStringSetAttribute(attrString,CFRangeMake(0,12),kCTForegroundColorAttributeName,red);

//kCTForegroundColorAttributeName,是CT定義的表示文本字體顏色的常量瞧甩,類似的常量茫茫多钉跷,組成了編輯富文本的諸多屬性。

//通過多屬性字符肚逸,可以得到一個文本顯示范圍的工廠對象爷辙,我們最后渲染文本對象是通過這個工廠對象進(jìn)行的。這部分需要引入#import<CoreText/CoreText.h>

CTFramesetterRef framesetter = CTFramesetterCreatWithAttributedString(attrString);

// 獲得要繪制的區(qū)域的高度

CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), nil, bouns, nil);

CGFloat textHeight = coreTextSize.height;//真實文本內(nèi)容的高度

//attrString 完成使命可以休息了

CFRelease(attrString)

//創(chuàng)建一個有文本內(nèi)容的范圍

CTFrameRef frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0,0),path,NULL);

//把內(nèi)容顯示在給定的文本范圍內(nèi)朦促;

CTFrameDraw(frame,context);

//完成任務(wù)就把我們創(chuàng)建的對象回收掉犬钢,內(nèi)存寶貴,不用了就回收思灰,這是好習(xí)慣玷犹。

CFRelease(frame),CTRelease(framesetter);CTRelease(path);

單行文本顯示

//初始化畫布、調(diào)整坐標(biāo)系、都和上文一樣歹颓。單行文本繪制有獨特的地方坯屿,當(dāng)然你用段落繪制的方式也是OK的,不過殺雞不要用牛刀巍扛,CTFrameDraw方法要比CTLineDraw方法需要更多時鐘周期领跛,所以還是用單行的繪制方法好。

//上文提到創(chuàng)建一個多屬性文本有很多方式撤奸,這里我就用一種包含不同屬性的字典的方式吠昭。

CFStringRef ?keys[] = { kCTFontAttributeName};//這里表示不同的字體。比如方正胧瓜、楷體之類的矢棚。

CFTypeRef ? values[] ={font};

CFDictionaryRef ?attributes = CFDictionaryCreate(kCFAllocatorDefault,(const void *)&keys,(const void *)values,sizeof(keys)/sizeof(keys[0])),&kCFTypeDictionaryKeyCallBacks,&kCFTypeDicitionaryValueCallBacks);

CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault,string,attributes);//上文用到的是replease的生成方法。

//每完成一步府喳,都要回頭看是否有可以釋放掉的已經(jīng)閑下來的對象蒲肋,這樣比最后在找好很多,也減少遺漏钝满。

CFRelease(string),CFRelease(attributes);

//獲得單行的內(nèi)容對象兜粘,對比上文中的CFFrame,這里就一句弯蚜,更簡單

CTLineRef line = CTlineCreatWithAttributedString(attrString);

//確定一個起點孔轴,就可以畫內(nèi)容了,畢竟內(nèi)容已經(jīng)有了

CGContextSetTextPostion(content,10.0,10.0);//文本的起點

CTLineDraw(line,context);

CFRelease(line),CFRelease(context);

豎版文本繪制

上面的用法中不論是段落還是單行碎捺,都是我們默認(rèn)的左右逐字排版的路鹰,但在實際生活中上下的排版也很常見,下面我們就學(xué)習(xí)一下豎版文本繪制牵寺。

//首先我們需要知道豎版中每一列的path才能去繪制

-(CFArrayRef)creatColumnsWithColumnCount:(int)colunCount{

int column;

CGRect *columnRects = (CGRect*)calloc(columnCount,sizeof(*columnRects))

columnRects[0] = self.bounds;//第一列覆蓋整個view,為下面循環(huán)得到列的rect做準(zhǔn)備悍引。

//把view的寬按照列數(shù)平分,當(dāng)然你也可以自定義不用平分

CGFloat columnWidth = CGRectGetWidth(self.bounds)/columnCount;

for (column = 0;column <columnCount-1;column++){//得到每一列的Rect恩脂,自動存在數(shù)組中

CGRectDivide(columnRects[column],&columnRects[column],columnRects[column+1],columnWidth,CGRectMinXEdge);

}

//給所有列增加幾個像素的邊距

for(column =0;column<columnCount;column++){

columnRects[column] = CGRectInset(columnRects[column],8.0,15.0);//8.0表示水平邊距帽氓,15.0表示縱向邊距。

}

//創(chuàng)建一個數(shù)組俩块,每一列的布局路徑

CFMutableArrayRef ?array = CFArrayCreateMutable(kAFAllocatorDefault,columnCount,&kCFTypeArrayCallBacks);

for(column=0;column<columnCount;column++){

CGMutablePathRef path = CGPathCreatMutable();

CGPathAddRect(path,NULL,columnRects[column]);

CFArrayInsertValueAtIndex(array,colum,path)

CFRelease(path);

}

free(columnRects)//清理指針黎休;

return array;

}

-(void)drawRect:(CGRect)rect{//重寫

//初始化一個畫布

CGContextRef context = UIGraphicsGetCurrentContext();

// Flip the context coordinates in iOS only.

CGContextTranslateCTM(context, 0, self.bounds.size.height);

CGContextScaleCTM(context, 1.0, -1.0);

CGContextSetTextMatrix(context, CGAffineTransformIdentity);

//完成準(zhǔn)備工作,類似段落的繪制

//獲得段落繪制的工廠對象:

CTFramesetterRef framesetter = CTFramesetterCreatWithAttributedString((CFAttributedStringRef)self.attributeString);

//獲得3列的文本路徑數(shù)組

CFArrayRef columnPaths= [self createColumnsWithColumnCount:3];

//開始一列一列繪制玉凯,就像一個個的段落

CFIndex pathCount = CFArrayGetCount(columnPaths);

CFIndex startIndex = 0;

for(column = 0;column <pathCount;column++){//循環(huán)繪制豎版文本势腮。

CGPathRef path = (CGPathRef)CFArrayGetValueAtIndex(columnPaths,column);

CTFrameRef ?frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(startIndex,0),path,NULL);

CTFrameDraw(frame,context);

CFRange frameRange = CTFrameGetVisibleStringRange(frame);//每一列在文本中范圍

startIndex += frameRange.length;

CFRelease(frame);

}

CFRelease(columnPaths);

CFRelease(framesetter);

}

手動斷行

一般文本都會自動斷行的,如果想要自動斷行就要看下面的了漫仆。

老規(guī)矩做文本繪制的準(zhǔn)備捎拯,這里代碼不寫了,上面有盲厌。

默認(rèn)完成幾個對象的創(chuàng)建:

double width; 從開始到需要斷行的寬度

CTContextRef context:畫布對象署照;

CGPoint textPosition:文本開始的點

CFAttributedStringRef: attrString:多屬性字符對象祸泪。

//從attrString獲得一個類型工廠對象typesetter

CTTypeSetterRef ?typesetter = CTTypesetterCreateWithAttributeString(attrString);

//

CFIndex start = 0;

CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start,width);//斷成幾行

//由斷行后產(chǎn)生的行數(shù),生成一個line對象建芙;

CTLineRef line = CTTypesetterCreateLine(typesetter,CFRangeMake(start,count));

CFRelease(typesetter);

//獲得中心線所要的偏移量没隘,畢竟不是自動斷行的,斷行后的中心線和原來的發(fā)生了偏移禁荸。

float flush = 0.5;//中心

double penOffset = CTLineGetPenoffsetForFlush(line, flush,width);

CGContextSetTextPositon(context,textPostion.x+penOffset,textPostion.y);

CTlineDraw(line,context);

start += count;//索引移到斷行符外右蒲。

CFRelease(line),CFRelease(context)

增加復(fù)雜度了

文本增加段落格式的應(yīng)用

NSAttibutedString *applyParaStyle(CFStringRef,fontName,CGFloat pointSize,NSString *plainText,CGFloat lineSpaceinc){

//通過行高來得到一個字體對象。

CTFontRef font = CTFontCreatWithName(fontName,pointSize,NULL);

//設(shè)置行距

CGFloat lineSpacing = (CTFontGetLeading(font) +lineSpaceInc)*2;//為毛行距是這樣的公式赶熟,更細(xì)節(jié)的以后再說瑰妄。

//一個段落設(shè)置對象

CTParagraphStyleSetting setting;

setting.spec = kCTParagraphStyleSpecifierLineSpacing;

setting.valueSize = sizeof(CGFloat);

setting.value = &lineSpacing;

CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(&seting,1);

//把段落格式添加到屬性字典中

NSDictionary *attribute = [NSDictionary dictionaryWithObjectsAndKeys:

(__birgde id)font, (id)kCTTFontNameAttribute,

(__birgde id)paragraphStyle,(id)kCTParagraphStyleAttributeName,nil];

CFRelease(font),CFRelease(paragraphStyle);

NSAttributedString *attrString = [NSAttributedString alloc]initWithString:plainText attributes:attribute];

CFRelease(attribute);

return attrString;

}

//同樣準(zhǔn)備工作默認(rèn)搞定,我們來創(chuàng)建一個有特定字體和行間距的文本钧大,action翰撑!

CFStringRef fontName = CFSTR("Didot Italic");//意大利字體

CGFloat pointSize = 24.0,行高24.0;

CFStringRef string = CFSTR("Hello, World! I know nothing in the world that has as much power as a word. Sometimes I write one,and I look at it, until it begins to shine");

//得到有屬性的字符

NSAttributeString * string = applyParaStyle(fontName,pointSize,(NSString*)string,50.0);

Notes:記得回收不用的C對象

CTFramesetterRef frameseter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string);//framesetter;

//需要一個路徑

CGPathRef path = CGPathCreateWithRect(rect,NULL);

//產(chǎn)生frame啊央,

CTFrame frame = CTFramesetterCreatFrame(frameseter,CFRangeMake(0,0),path,NULL);

CTFrameDraw(frame,context);

//Release 回收不用的對象要經(jīng)常記得眶诈。

CoreText,之所以功能強(qiáng)大,除了可以在規(guī)則的區(qū)域內(nèi)水平瓜饥、豎直的排版外逝撬,還可以在任意非矩形區(qū)域內(nèi)展示文本。

非矩形區(qū)域的文本顯示

這里從過一個甜甜圈形狀的舉例說明乓土。也就是一個環(huán)

static void AddSquashedDonutPath(CGMutablePathRef path,const CGAffineTransform* m,CGRect rect){

CGFloat width = CGRectGetWidth(rect);CGFloat height = CGRectGetHeight(rect);

CGFloat radiusH = width/3.0宪潮;CGFloat radiusV = height/3.0;

CGPathMoveToPoint(path, m,rect.origin.x,rect.origin.y+height-radiusV);

CGPathAddQuadCurveToPoint(path ,m, ?rect.origin.x, rect.origin.y +height, rect.origin.x+radiusH, rect.origin.y + height);//一個貝塞爾曲線

CGPathAddLineToPoint( path, m, rect.origin.x + width - radiusH,rect.origin.y + height);//直線

CGPathAddQuadCurveToPoint( path, m, rect.origin.x + width,rect.origin.y + height,rect.origin.x + width,

rect.origin.y + height - radiusV);

CGPathAddLineToPoint( path, m, rect.origin.x + width,rect.origin.y + radiusV);

CGPathAddQuadCurveToPoint( path, m, rect.origin.x + width, rect.origin.y,rect.origin.x + width - radiusH, rect.origin.y);

CGPathAddLineToPoint( path, m, rect.origin.x + radiusH, rect.origin.y);

CGPathAddQuadCurveToPoint( path, m, rect.origin.x, rect.origin.y,rect.origin.x, rect.origin.y + radiusV);

CGPathCloseSubpath( path);//完成并關(guān)閉這個路徑

CGPathAddEllipseInRect( path, m,CGRectMake( rect.origin.x + width / 2.0 - width / 5.0,rect.origin.y + height / 2.0 - height / 5.0,width / 5.0 * 2.0, height / 5.0 * 2.0));//畫內(nèi)圈,添加一個橢圓在矩形內(nèi)趣苏。

}

-(NSArry *)paths{

CGMutablePathRef path = CGPathCreatMutable();

CGRect bounds = self.bounds;

bounds = CGRectInset(bounds,10.0,10.0);

AddSquashedDonutPath(path,NULL,bounds);

NSMutableArray *result = [NSMutableArray arrayWithObject:CFBridgingRelease(path)];

return result;

}

CFBridgingRelease():將一個CoreFoundation對象轉(zhuǎn)換為OC對象狡相,并將對象所有權(quán)轉(zhuǎn)給ARC,我們不必手動釋放食磕,如果在一個純CF中就需要手動處理尽棕。

CFBridgingRetain():將一個OC對象轉(zhuǎn)化為CF對象,并獲得對象的所有權(quán)彬伦,所以我們得負(fù)責(zé)釋放對象滔悉。例如:

NSString *string = @"this is string";

CFStringRef cfString = (CFStringRef)CFBridgingRetain(string);

CFRelease(cfString);

-(void)drawRect:(CGRect)rect{

[super drawRect:rect];

//準(zhǔn)備工作略過

CFStringRef textString = CFSTR("Hello, World! I know nothing in the world that has as much power as a word. Sometimes I write one, and I look at it,until it begins to shine.");

CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);

CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), textString);

//增加一個顏色的屬性

CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();

CGFloat components[] = { 1.0, 0.0, 0.0, 0.8 };

CGColorRef red = CGColorCreate(rgbColorSpace, components);

CGColorSpaceRelease(rgbColorSpace)

CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 13),kCTForegroundColorAttributeName, red);

//得到一個工廠對象

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);

//path 路徑數(shù)組。

NSArray *paths = [self paths];

CFIndex startIndex = 0;

//聲明幾個顏色的宏

#define GREEN_COLOR [UIColor greenColor]

#define YELLOW_COLOR [UIColor yellowColor]

#define BLACK_COLOR [UIColor blackColor]

for(id object in paths){

CGPathRef path = (__bridge CGPathRef)object;

//甜甜圈設(shè)置一個黃色的背景

CGContextSetFillColorWithColor(context,[YELLOW_COLOR CGColor])?

CGContextAddPath(context,path);

CGContextFillPath(context);

//給路徑描邊

CGContextDrawPath(context,kCGPathStroke);

//

CTFrameRef frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(startIndex,0),path,NULL);

CTFrameDraw(frame,context);

文本在甜甜圈中顯示单绑,可能顯示不完回官,或者不夠顯示,它后面的內(nèi)容需要知道搂橙,這次文本繪制的有效范圍歉提,為下面的繪制做好準(zhǔn)備。

CFRangeRef range = CTFrameGetVisibleStringRange(frame);

startIndex += frameRange.length;

CFRelease(frame);

}

CFRelease(attrString);

CFRelease(framesetter);

}

上文我們主要講了文本繪制的宏觀布局方面,下面講一下對字體的操作苔巨,變色弯屈、加粗等等

操作字體

1、字體是否加粗恋拷。

CTFontRef CreateBoldFont(CTFontRef font ,Boolen makeBold){

CTFontSymbolicTraits desireTrait = 0;字體性狀

CTFontSymbolicTraits traitMask;

if(makeBold) desireTrait = kCTFontBoldTrait;

traitMask = kCTFontBoldTrait;

//開始轉(zhuǎn)換资厉,失敗返回NULL

return CTFontCreateCopyWithSymbolicTraits(font,0.0,NULL,desireTrait,traitMask)

}

2、字體和序列化數(shù)據(jù)之間的轉(zhuǎn)換蔬顾。如何創(chuàng)建XML數(shù)據(jù)來序列化可以嵌入到文檔中的字體宴偿。

CFDataRef CreateFlattenedFontData(CTFontRef font){

CFDataRef result = NULL;

CTFontDescriptorRef descriptor;

CFDictionaryRef ? ?attributes;

//從字體獲得到字體描述對象

descriptor = CTFontCopyFontDescriptor(font);

if(descriptor !=NULL){

attributes = CTFontDescriptorCopyAttributes(descriptor);

if(attributes !=NULL){

if (CFPropertyListIsValid(attributes,kcFPropertyListXMLFormat_v1_0)){

result = CFPropertyListCreateXMLData(kCFAllocatorDefault, attributes);

}

}

}

return font;

}

CTFontRef CreateFontFromFlattenedFontData(CFDataRef iData){//data得到font

CTFontRef? ? ? ? ? font = NULL;

CFDictionaryRef? ? attributes;

CTFontDescriptorRef descriptor;

//

attributes =(CFDictionaryRef)CFPropertyListCreateFromXMLData(

kCFAllocatorDefault,iData, kCFPropertyListImmutable, NULL);

if (attributes != NULL) {

descriptor = CTFontDescriptorCreateWithAttributes(attributes);

if (descriptor != NULL) {

font = CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL);

}

}

return font;

}

如何實現(xiàn)圖文混排

上面我們處理的對象只有文字,僅對文字做好處理诀豁,還是不行的窄刘,無圖誰看呀,下面我就學(xué)習(xí)怎么處理圖片在CoreText中舷胜,在上面的文章中娩践,我們用到了,CTFramesetterRef,CTFrame,CTLine,CTTypeSetter等烹骨,就是沒有用到CTRun,下面就用到翻伺。

在CTFrame內(nèi)部,是由多個CTline組成沮焕,每個CTLine代表一行吨岭,每個CTLine又有若干CTRun組成,每個CTRun代表一組顯示風(fēng)格一致的文本峦树。我們不用管理CTLine和CTRun的創(chuàng)建辣辫。

我們在文本中曾經(jīng)改變過字體的大小和顏色,在一行中如果這些屬性都不同魁巩,那么就是有不同CTRun來組成的一個CTline急灭。

雖然我們不用管理CTRun的創(chuàng)建過程,但是我們可以設(shè)置CTRun的CTRunDelegate來制定文本繪制的高度谷遂、寬度葬馋、對齊方式。

對于圖片的排版埋凯,CoreText本質(zhì)是不支持的点楼,但是我們可以在需要的地方扫尖,用特殊的空白字符代替白对,同時設(shè)置改字體的CTRunDelegate信息為要顯示的圖片。這樣最后成的CTFrame换怖,就會在繪制時將圖片等的位置預(yù)留出來甩恼。

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];

}

在使用的時候:

CTRunDelegateCallbacks callbacks;

memset(&callbacks,0,sizeof(CTRunDelegateCallbacks));

callbacks.versin = kCTRunDelegateVersion1;

callbacks.getAscent = ascentCallback;

callbacks.getDescent =descentCallback;

callbacks.getWidth = widthCallback;

CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks,(__bridge void *)(dict))//dict 為文本屬性的字典。這里是針對圖片信息的字典,寬高之類

//使用0xFFFC作為空白的占位符

unichar objectReplacementChar = 0xFFFC;

NSString *content =[NSString stringWithCharacters:&objectReplacementChar length:1];

NSDictionary *attributes = [self xxxxxxx];//獲得文本整體的風(fēng)格字典条摸,比如行間距悦污,字體大小之類的信息。

//

NSMutabelAttributtedString *space = [NSMutabelAttributtedString alloc]initWithString:content attributes:attributes]

CFAttributedStringSetAttribute((CFMutableAttributedStringRef)space,CFRangeMake(0,1),kCTRunDelegateAttributedName,delegate);

//得到一個包含圖片占位符的一個多屬性字符串钉蒲。這是每個CTRun的情況切端,多個CTRun才能組成一個CTLine

在整體繪制之前,我們要得到所有圖片在CTFrame中位置

NSArray *lines = (NArray*)CTFrameGetlines(self.ctFrame);

int lineCount = [Lines count];

CGPoint lineOrigins[lineCount];//每行開始的坐標(biāo)

CTFrameGetlineOrigins(self.ctFrame,CFRangMake(0,0),lineOrigins);

int imgIndex = 0;

imageData = self.imageArray[0];//就是圖片數(shù)組中第一個的圖片信息對象顷啼。

for(int i = 0;i<lineCount;i++){

if(imageDate == nil)break;

CTLineRef line = (__bridge CTLineRef)lines[i];

NSArray *runObjArray = (NSArray *)CTLineGeGlyphRuns(line);//每行有幾個CTRun

//

for (id runObj in runObjArray){

CTRunRef run = (__bridge CTRunRef)runObj;

NSDictionary *runAttributes = (NSDictionary*)CTRunGetAttributes(run);

CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForkey:(id)kCTRunDelegateAttributdeName];

if (delegate == nil){ continue;}

NSDictionary*metaDic = CTRunDelegatGetRefCon(delegate);//獲得圖片元數(shù)據(jù)

if(![meteDic isKingOfClass[NDDictionary class]]){ continue;}

CGRect runBounds; CGFloat ascent;CGFloat descent;

runBounds.size.width = CTRunGetTypegraphicBounds(run,CFRangeMake(0,0),&ascent,&descent,NULL);

runBounds.size.height = ascent + descent;

CGFloat xOffset ?= CTLineGetOffsetForStringIndex(line,CTRunGetStringRange(run).location,NULL);

runBounds.origin.x = lineOrigins[i].x+xOffset;

runBounds.origin.y = lineOrigins[i].y;

runBounds.origin.y -= descent;

CGPathRef pathRef = CTFrameGetPath(self.ctFrame);

CGRect colRect = CGPathGetBoundingBox(pathRef);//返回路徑的一個邊界框踏枣;

CGRect delegateBounds = CGRectOffset(runBounds,colRect.origin.x,colRect.origin.y);//圖片對象的范圍。

imageData.imagePositon = delegateBounds;

imgIndex++;

if(imgIndex == self.imageArray.count){

imageData = nil;break;

}else{

imageData = self.ImageArray[ImgIndex];

}

}

}

獲得到文本中圖片的位置信息以后钙蒙,網(wǎng)絡(luò)加載的圖片可以通過異步得到數(shù)據(jù)在一起繪制出來茵瀑。

for (CoreTextImageData * imageData in self.data.imageArray) {

UIImage *image = [UIImage imageNamed:imageData.name];

if (image) {

CGContextDrawImage(context, imageData.imagePosition, image.CGImage);//繪制圖片

}

}

圖文繪制完成之后,由于我們是通過CoreText繪制躬厌,它存在一個缺點就是不能象webview和uitextview等OC控件可以點擊马昨、長按、復(fù)制粘貼等扛施。我們可以給文本添加手勢來解決這個問題鸿捧。添加手勢的重點在于,判斷作用點的位置疙渣。下面就把判斷位置的方法放出來笛谦。為圖片添加點擊事件。

-(void)userTapGesture:(UIGestureRecognizer*)recognizer{

CGPoint point = [recognizer locationInView:self];

for (CoreTextImageData * imageData in self.data.imageArray) {

// 翻轉(zhuǎn)坐標(biāo)系昌阿,因為 imageData 中的坐標(biāo)是 CoreText 的坐標(biāo)系

CGRect imageRect = imageData.imagePosition;

CGPoint imagePosition = imageRect.origin;

imagePosition.y = self.bounds.size.height - imageRect.origin.y-imageRect.size.height;

CGRect rect = CGRectMake(imagePosition.x, imagePosition.y, imageRect.size.width, imageRect.size.height);

// 檢測點擊位置 Point 是否在 rect 之內(nèi)

if (CGRectContainsPoint(rect, point)) {

// 在這里處理點擊后的邏輯

NSLog(@"bingo");

break;

}

}

}

文本添加鏈接

添加鏈接和添加圖片類似饥脑,不過更簡單一些,這些需要特別處理懦冰,都要一個數(shù)組去承載它們灶轰。

//檢測點擊位置是否在連接上

+(CoreTextLinkData*)touchLinkInView:(UIView*)view atPoint:(CGPoint)point data:(coreTextData*)data{

CTFrameRef textframe = data.ctFrame;

CFArrayRef lines = CTFrameGeLines(textframe);

if(!lines) return nil;

CFIndex count = CFArrayGetCount(lines);

CoreTextLineData*foundLink = nil;

//獲得每一行origin坐標(biāo)

CGPoint origins[count];

CTFrameGetLineOrigins(textframe,CFRangeMake(0.0),origins);

// 翻轉(zhuǎn)坐標(biāo)系

CGAffineTransform transform =? CGAffineTransformMakeTranslation(0, view.bounds.size.height);

transform = CGAffineTransformScale(transform, 1.f, -1.f);

for (int i = 0; i < count; i++) {

CGPoint linePoint = origins[i];

CTLineRef line = CFArrayGetValueAtIndex(lines, i);

// 獲得每一行的 CGRect 信息

CGRect flippedRect = [self getLineBounds:line point:linePoint];

CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);

if (CGRectContainsPoint(rect, point)) {

// 將點擊的坐標(biāo)轉(zhuǎn)換成相對于當(dāng)前行的坐標(biāo)

CGPoint relativePoint = CGPointMake(point.x-CGRectGetMinX(rect),

point.y-CGRectGetMinY(rect));

// 獲得當(dāng)前點擊坐標(biāo)對應(yīng)的字符串偏移

CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint);

// 判斷這個偏移是否在我們的鏈接列表中

foundLink = [self linkAtIndex:idx linkArray:data.linkArray];

return foundLink;

}

}

}

+ (CGRect)getLineBounds:(CTLineRef)line point:(CGPoint)point {

CGFloat ascent = 0.0f;

CGFloat descent = 0.0f;

CGFloat leading = 0.0f;

CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);

CGFloat height = ascent + descent;

return CGRectMake(point.x, point.y - descent, width, height);

}

+ (CoreTextLinkData *)linkAtIndex:(CFIndex)i linkArray:(NSArray *)linkArray {

CoreTextLinkData *link = nil;

for (CoreTextLinkData *data in linkArray) {

if (NSLocationInRange(i, data.range)) {

link = data;

break;

}

}

return link;

}

到這里CoreText的基本用法已經(jīng)介紹完畢,基本可以完成一個排版功能了刷钢,這篇文章是我學(xué)習(xí)過程的記錄笋颤,有助于自己日后查看。

在學(xué)習(xí)的過程中參考查看了唐巧大神的文章:http://blog.devtang.com/2015/06/27/using-coretext-2/

完整的demo内地,可以看看唐巧大神博客中的地址伴澄。文章中更多是本人學(xué)習(xí)的理解。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阱缓,一起剝皮案震驚了整個濱河市非凌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荆针,老刑警劉巖敞嗡,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颁糟,死亡現(xiàn)場離奇詭異,居然都是意外死亡喉悴,警方通過查閱死者的電腦和手機(jī)棱貌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箕肃,“玉大人婚脱,你說我怎么就攤上這事∩紫瘢” “怎么了起惕?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咏删。 經(jīng)常有香客問我惹想,道長,這世上最難降的妖魔是什么督函? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任嘀粱,我火速辦了婚禮,結(jié)果婚禮上辰狡,老公的妹妹穿的比我還像新娘锋叨。我一直安慰自己,他們只是感情好宛篇,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布娃磺。 她就那樣靜靜地躺著,像睡著了一般叫倍。 火紅的嫁衣襯著肌膚如雪偷卧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天吆倦,我揣著相機(jī)與錄音听诸,去河邊找鬼。 笑死蚕泽,一個胖子當(dāng)著我的面吹牛晌梨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播须妻,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼仔蝌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了荒吏?” 一聲冷哼從身側(cè)響起敛惊,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎司倚,沒想到半個月后豆混,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡动知,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年皿伺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盒粮。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸵鸥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丹皱,到底是詐尸還是另有隱情妒穴,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布摊崭,位于F島的核電站讼油,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏呢簸。R本人自食惡果不足惜矮台,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望根时。 院中可真熱鬧瘦赫,春花似錦、人聲如沸蛤迎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽替裆。三九已至校辩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辆童,已是汗流浹背召川。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留胸遇,地道東北人荧呐。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像纸镊,于是被迫代替她去往敵國和親倍阐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容