CoreText是iOS/OSX中文本顯示的一個底層框架,它是用C語言寫成的,有快速簡單的優勢。iOS中的Textkit,webkit都是基于CoreText封裝的。本文討論一下CoreText的一些常規用法,知道它有哪些功能,我們在開發中遇到OC的類解決不了的問題,或者需要提高性能的時候,可以使用它們。我喜歡用代碼說話,一步一步走:
先了解CoreText的機構圖
普通段落文本的顯示
//1、初始化一個畫布
CGContextRef context = UIGraphicsGetCurrentContext();
//2、反轉畫布的坐標系,由于OS中坐標系原點在左下角,iOS中在左上角,所以在iOS需要反轉一下,OS中不用。
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
//3、設置文本的矩陣
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
//創建文本范圍的路徑
CGMutablePathRef ?path = CGPathCreateMutable();
//:創建一個矩形文本區域。
CGRect bouns = CGRectMake(10.0,10.0,200.0,200.0);
CGPathAddRect(path,NULL,bounds);
完成了文本區域的設置,開始準備顯示的素材,這里就顯示一段文字。
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.");
//創建一個多屬性字段,maxlength為0;maxlength是提示系統有需要多少內部空間需要保留,0表示不用提示限制。
CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
//為attrString添加內容,也可以用CFAttributedStringCreate 開頭的幾個方法,根據不同的需要和參數選擇合適的方法。這里用替換的方式,完成寫入。
CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0),textString);//此時attrString就有內容了
//為多屬性字符串增添一個顏色的屬性,這里就是奇妙之處,富文本的特點就在這里可以自由的調整文本的屬性:比如,顏色,大小,字體,下劃線,斜體,字間距,行間距等
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();//創建一個顏色容器對象,這里我們用RGB,當然還有很多其他顏色容器對象,可以根據需要和方便自由搭配,總有一款適合你的。
//創建一個紅色的對象
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定義的表示文本字體顏色的常量,類似的常量茫茫多,組成了編輯富文本的諸多屬性。
//通過多屬性字符,可以得到一個文本顯示范圍的工廠對象,我們最后渲染文本對象是通過這個工廠對象進行的。這部分需要引入#import<CoreText/CoreText.h>
CTFramesetterRef framesetter = CTFramesetterCreatWithAttributedString(attrString);
// 獲得要繪制的區域的高度
CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), nil, bouns, nil);
CGFloat textHeight = coreTextSize.height;//真實文本內容的高度
//attrString 完成使命可以休息了
CFRelease(attrString)
//創建一個有文本內容的范圍
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0,0),path,NULL);
//把內容顯示在給定的文本范圍內;
CTFrameDraw(frame,context);
//完成任務就把我們創建的對象回收掉,內存寶貴,不用了就回收,這是好習慣。
CFRelease(frame),CTRelease(framesetter);CTRelease(path);
單行文本顯示
//初始化畫布、調整坐標系、都和上文一樣。單行文本繪制有獨特的地方,當然你用段落繪制的方式也是OK的,不過殺雞不要用牛刀,CTFrameDraw方法要比CTLineDraw方法需要更多時鐘周期,所以還是用單行的繪制方法好。
//上文提到創建一個多屬性文本有很多方式,這里我就用一種包含不同屬性的字典的方式。
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的生成方法。
//每完成一步,都要回頭看是否有可以釋放掉的已經閑下來的對象,這樣比最后在找好很多,也減少遺漏。
CFRelease(string),CFRelease(attributes);
//獲得單行的內容對象,對比上文中的CFFrame,這里就一句,更簡單
CTLineRef line = CTlineCreatWithAttributedString(attrString);
//確定一個起點,就可以畫內容了,畢竟內容已經有了
CGContextSetTextPostion(content,10.0,10.0);//文本的起點
CTLineDraw(line,context);
CFRelease(line),CFRelease(context);
豎版文本繪制
上面的用法中不論是段落還是單行,都是我們默認的左右逐字排版的,但在實際生活中上下的排版也很常見,下面我們就學習一下豎版文本繪制。
//首先我們需要知道豎版中每一列的path才能去繪制
-(CFArrayRef)creatColumnsWithColumnCount:(int)colunCount{
int column;
CGRect *columnRects = (CGRect*)calloc(columnCount,sizeof(*columnRects))
columnRects[0] = self.bounds;//第一列覆蓋整個view,為下面循環得到列的rect做準備。
//把view的寬按照列數平分,當然你也可以自定義不用平分
CGFloat columnWidth = CGRectGetWidth(self.bounds)/columnCount;
for (column = 0;column <columnCount-1;column++){//得到每一列的Rect,自動存在數組中
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表示縱向邊距。
}
//創建一個數組,每一列的布局路徑
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);
//完成準備工作,類似段落的繪制
//獲得段落繪制的工廠對象:
CTFramesetterRef framesetter = CTFramesetterCreatWithAttributedString((CFAttributedStringRef)self.attributeString);
//獲得3列的文本路徑數組
CFArrayRef columnPaths= [self createColumnsWithColumnCount:3];
//開始一列一列繪制,就像一個個的段落
CFIndex pathCount = CFArrayGetCount(columnPaths);
CFIndex startIndex = 0;
for(column = 0;column <pathCount;column++){//循環繪制豎版文本。
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);
}
手動斷行
一般文本都會自動斷行的,如果想要自動斷行就要看下面的了。
老規矩做文本繪制的準備,這里代碼不寫了,上面有。
默認完成幾個對象的創建:
double width; 從開始到需要斷行的寬度
CTContextRef context:畫布對象;
CGPoint textPosition:文本開始的點
CFAttributedStringRef: attrString:多屬性字符對象。
//從attrString獲得一個類型工廠對象typesetter
CTTypeSetterRef ?typesetter = CTTypesetterCreateWithAttributeString(attrString);
//
CFIndex start = 0;
CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start,width);//斷成幾行
//由斷行后產生的行數,生成一個line對象;
CTLineRef line = CTTypesetterCreateLine(typesetter,CFRangeMake(start,count));
CFRelease(typesetter);
//獲得中心線所要的偏移量,畢竟不是自動斷行的,斷行后的中心線和原來的發生了偏移。
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)
增加復雜度了
文本增加段落格式的應用
NSAttibutedString *applyParaStyle(CFStringRef,fontName,CGFloat pointSize,NSString *plainText,CGFloat lineSpaceinc){
//通過行高來得到一個字體對象。
CTFontRef font = CTFontCreatWithName(fontName,pointSize,NULL);
//設置行距
CGFloat lineSpacing = (CTFontGetLeading(font) +lineSpaceInc)*2;//為毛行距是這樣的公式,更細節的以后再說。
//一個段落設置對象
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;
}
//同樣準備工作默認搞定,我們來創建一個有特定字體和行間距的文本,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);
//產生frame,
CTFrame frame = CTFramesetterCreatFrame(frameseter,CFRangeMake(0,0),path,NULL);
CTFrameDraw(frame,context);
//Release 回收不用的對象要經常記得。
CoreText,之所以功能強大,除了可以在規則的區域內水平、豎直的排版外,還可以在任意非矩形區域內展示文本。
非矩形區域的文本顯示
這里從過一個甜甜圈形狀的舉例說明。也就是一個環
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);//完成并關閉這個路徑
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));//畫內圈,添加一個橢圓在矩形內。
}
-(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對象轉換為OC對象,并將對象所有權轉給ARC,我們不必手動釋放,如果在一個純CF中就需要手動處理。
CFBridgingRetain():將一個OC對象轉化為CF對象,并獲得對象的所有權,所以我們得負責釋放對象。例如:
NSString *string = @"this is string";
CFStringRef cfString = (CFStringRef)CFBridgingRetain(string);
CFRelease(cfString);
-(void)drawRect:(CGRect)rect{
[super drawRect:rect];
//準備工作略過
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 路徑數組。
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;
//甜甜圈設置一個黃色的背景
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);
文本在甜甜圈中顯示,可能顯示不完,或者不夠顯示,它后面的內容需要知道,這次文本繪制的有效范圍,為下面的繪制做好準備。
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;
//開始轉換,失敗返回NULL
return CTFontCreateCopyWithSymbolicTraits(font,0.0,NULL,desireTrait,traitMask)
}
2、字體和序列化數據之間的轉換。如何創建XML數據來序列化可以嵌入到文檔中的字體。
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;
}
如何實現圖文混排
上面我們處理的對象只有文字,僅對文字做好處理,還是不行的,無圖誰看呀,下面我就學習怎么處理圖片在CoreText中,在上面的文章中,我們用到了,CTFramesetterRef,CTFrame,CTLine,CTTypeSetter等,就是沒有用到CTRun,下面就用到。
在CTFrame內部,是由多個CTline組成,每個CTLine代表一行,每個CTLine又有若干CTRun組成,每個CTRun代表一組顯示風格一致的文本。我們不用管理CTLine和CTRun的創建。
我們在文本中曾經改變過字體的大小和顏色,在一行中如果這些屬性都不同,那么就是有不同CTRun來組成的一個CTline。
雖然我們不用管理CTRun的創建過程,但是我們可以設置CTRun的CTRunDelegate來制定文本繪制的高度、寬度、對齊方式。
對于圖片的排版,CoreText本質是不支持的,但是我們可以在需要的地方,用特殊的空白字符代替,同時設置改字體的CTRunDelegate信息為要顯示的圖片。這樣最后成的CTFrame,就會在繪制時將圖片等的位置預留出來。
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];//獲得文本整體的風格字典,比如行間距,字體大小之類的信息。
//
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];//每行開始的坐標
CTFrameGetlineOrigins(self.ctFrame,CFRangMake(0,0),lineOrigins);
int imgIndex = 0;
imageData = self.imageArray[0];//就是圖片數組中第一個的圖片信息對象。
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);//獲得圖片元數據
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];
}
}
}
獲得到文本中圖片的位置信息以后,網絡加載的圖片可以通過異步得到數據在一起繪制出來。
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控件可以點擊、長按、復制粘貼等。我們可以給文本添加手勢來解決這個問題。添加手勢的重點在于,判斷作用點的位置。下面就把判斷位置的方法放出來。為圖片添加點擊事件。
-(void)userTapGesture:(UIGestureRecognizer*)recognizer{
CGPoint point = [recognizer locationInView:self];
for (CoreTextImageData * imageData in self.data.imageArray) {
// 翻轉坐標系,因為 imageData 中的坐標是 CoreText 的坐標系
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 之內
if (CGRectContainsPoint(rect, point)) {
// 在這里處理點擊后的邏輯
NSLog(@"bingo");
break;
}
}
}
文本添加鏈接
添加鏈接和添加圖片類似,不過更簡單一些,這些需要特別處理,都要一個數組去承載它們。
//檢測點擊位置是否在連接上
+(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坐標
CGPoint origins[count];
CTFrameGetLineOrigins(textframe,CFRangeMake(0.0),origins);
// 翻轉坐標系
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)) {
// 將點擊的坐標轉換成相對于當前行的坐標
CGPoint relativePoint = CGPointMake(point.x-CGRectGetMinX(rect),
point.y-CGRectGetMinY(rect));
// 獲得當前點擊坐標對應的字符串偏移
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的基本用法已經介紹完畢,基本可以完成一個排版功能了,這篇文章是我學習過程的記錄,有助于自己日后查看。
在學習的過程中參考查看了唐巧大神的文章:http://blog.devtang.com/2015/06/27/using-coretext-2/
完整的demo,可以看看唐巧大神博客中的地址。文章中更多是本人學習的理解。