iOS-CoreText 的使用
CoreText是iOS/OSX中文本显示的一个底层框架,它是用C语言写成的,有快速简单的优势。iOS中的Textkit,webkit都是基于CoreText封装的。本文讨论一下CoreText的一些常规用法,知道它有哪些功能,我们在开发中遇到OC的类解决不了的问题,或者需要提高性能的时候,可以使用它们。我喜欢用代码说话,一步一步走:
先了解CoreText的机构图
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,可以看看唐巧大神博客中的地址。文章中更多是本人学习的理解。