美文网首页iOS开发
iOS-CoreText 的使用

iOS-CoreText 的使用

作者: 小猫仔 | 来源:发表于2017-10-01 11:29 被阅读1765次

    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,可以看看唐巧大神博客中的地址。文章中更多是本人学习的理解。

    相关文章

      网友评论

        本文标题:iOS-CoreText 的使用

        本文链接:https://www.haomeiwen.com/subject/bsqiextx.html