• iOS-CoreText的那些事【电子书的那些事】


    这段时间在搞电子书,把这些天出现的问题归总下,我还是希望电子书的格式包括返回的数据,可直观的反应出客户端想表达的内容;原生的体验还是比较好的,希望对coretext再深入。

    1.判断点击的位置是否在某个NSRange范围内

    #pragma mark 判断点击的位置是否在 range内
    /**
     将点击的位置转换成字符串的偏移量,如果没有找到,则返回-1
     view:view
     point:点击位置
     frameRef:当前view的frameRef
     */
    + (CFIndex)wkj_touchContentOffsetInView:(UIView *)view
                                point:(CGPoint)point
                               frameRef:(CTFrameRef)frameRef{
        CTFrameRef textFrame = frameRef;
        CFArrayRef lines = CTFrameGetLines(textFrame);
        if (!lines) {
            return -1;
        }
        CFIndex count = CFArrayGetCount(lines);
        
        // 获得每一行的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);
        
        CFIndex idx = -1;
        for (int i = 0; i < count; i++) {
            CGPoint linePoint = origins[i];
            CTLineRef line = CFArrayGetValueAtIndex(lines, i);
            // 获得每一行的CGRect信息
            CGRect flippedRect = [self wkj_getLineBounds:line point:linePoint];
            CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);
            
            if (CGRectContainsPoint(rect, point)) {
                // 将点击的坐标转换成相对于当前行的坐标cocoapods
                CGPoint relativePoint = CGPointMake(point.x-CGRectGetMinX(rect),
                                                    point.y-CGRectGetMinY(rect));
                // 获得当前点击坐标对应的字符串偏移
                idx = CTLineGetStringIndexForPosition(line, relativePoint);
            }
        }
        return idx;
    }
    
    + (CGRect)wkj_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);
    }
    
    /**
     此处判断
     根据上面返回的字符串的偏移量,对比range是否在点击的位置
     */
    + (BOOL)wkj_judgeAtIndex:(CFIndex)index range:(NSRange)range {
        if (NSLocationInRange(index, range)) {
            ///点击处在range内
            return YES;
        }
        return NO;
    }

    2.通过CFRange与CTFrameRef获取要操作(涂色,划线)的CGRect数组

    #pragma mark 通过range获取rect集合【用于划线、涂色】
    + (NSArray*)wkj_frameGetFrameOfStringInRange:(CTFrameRef)frameRef range:(CFRange)range{
        CFRange frameRefRange = CTFrameGetStringRange(frameRef);
        range = [self wkj_cfRangeInsection:frameRefRange range:range];
        if ([self wkj_cfRangeEqualToRange:range range:CFRangeZero]) {
            return nil;
        }
        
        NSArray *lines = (NSArray*)CTFrameGetLines(frameRef);
        NSInteger lineCount = [lines count];
        //获取整个CTFrame的大小
        CGPathRef path = CTFrameGetPath(frameRef);
        CGRect frameRefRect = CGPathGetBoundingBox(path);
        //获取所有行的起点
        CGPoint *origins = (CGPoint*)malloc(lineCount * sizeof(CGPoint));
        CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), origins);
        NSMutableArray *rects = [NSMutableArray array];
        for (CFIndex index = 0; index<lines.count; index++) {
            CTLineRef line = (__bridge CTLineRef)[lines objectAtIndex:index];
            CFRange rangeOfLine = CTLineGetStringRange(line);
            CFRange rangeOfInsection = [self wkj_cfRangeInsection:rangeOfLine range:range];
            if (![self wkj_cfRangeEqualToRange:rangeOfInsection range:CFRangeZero]) {
                CGRect frame = [self wkj_lineGetFrame:line point:origins[index] range:rangeOfInsection];
                frame = CGRectOffset(frame, frameRefRect.origin.x, frameRefRect.origin.y);
                
                [rects addObject:[NSValue valueWithCGRect:frame]];
            }
        }
        free(origins);
        return [rects copy];
    }
    
    + (CGRect)wkj_lineGetFrame:(CTLineRef)line
                         point:(CGPoint)origin
                         range:(CFRange)range{
        CFRange lineRange = CTLineGetStringRange(line);
        range = [self wkj_cfRangeInsection:lineRange range:range];
        CGFloat trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(line);
        if ([self wkj_cfRangeGetEndLocation:range]==[self wkj_cfRangeGetEndLocation:lineRange]&&trailingWhitespaceWidth>0) {
            --range.length;
        }
        CGFloat xStart = CTLineGetOffsetForStringIndex(line, range.location, NULL);
        CGFloat xEnd = CTLineGetOffsetForStringIndex(line, [self wkj_cfRangeGetEndLocation:range]+1, NULL);
        
        CGFloat ascent, descent, leading;
        
        CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
        CGRect selectionRect = CGRectMake(origin.x + xStart,
                                          origin.y - descent,
                                          xEnd - xStart,
                                          ascent + descent + leading);
        
        return selectionRect;
    }
    
    + (CFRange)wkj_cfRangeInsection:(CFRange)range1 range:(CFRange)range2{
        if ([self wkj_cfRangeGreaterThanRange:range1 range:range2]||[self wkj_cfRangeGreaterThanRange:range2 range:range1]) {
            return CFRangeZero;
        }
        CFIndex beginLocation = MAX(range1.location, range2.location);
        CFIndex endLocation = MIN([self wkj_cfRangeGetEndLocation:range1], [self wkj_cfRangeGetEndLocation:range2]);
        
        return [self wkj_cfRangeMakeWithInterval:beginLocation endIndex:endLocation];
    }
    
    + (BOOL)wkj_cfRangeGreaterThanRange:(CFRange)range1 range:(CFRange)range2{
        return range1.location>[self wkj_cfRangeGetEndLocation:range2];
    }
    + (CFIndex)wkj_cfRangeGetEndLocation:(CFRange)range{
        return range.location+range.length-1;
    }
    + (CFRange)wkj_cfRangeMakeWithInterval:(CFIndex)startIndex endIndex:(CFIndex)endIndex{
        return CFRangeMake(startIndex, endIndex - startIndex + 1);
    }
    + (BOOL)wkj_cfRangeEqualToRange:(CFRange)range1 range:(CFRange)range2{
        return range1.location == range2.location&&range1.length==range2.length;
    }

     3.涂色划线

    - (void)wkj_drawLineForSelectArea{
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetFillColorWithColor(context, [[UIColor redColor] colorWithAlphaComponent:0.3].CGColor);
        for (NSValue *rectValue in _arraySelectRect) {
            CGRect rect = [rectValue CGRectValue];
            rect.origin.y -= 2.5;
            ///划线时可设置高度为1  涂色是可根据字体大小设置
            rect.size.height = 1;
            rect.size.width -= 1.5;
            CGContextFillRect(context, rect);
        }
    }

     4.CoreText 分页

    #pragma mark 分页
    /**
     CoreText 分页
     str: NSAttributedString属性字符串
     textFrame: 绘制区域
     */
    
    + (NSArray *)wkj_coreTextPaging:(NSAttributedString *)str textArea:(CGRect)textFrame{
        ///分页NSAttributedString结果
        // NSMutableArray *arrayPagingStr = [NSMutableArray array];
        ///分页NSRange结果
        NSMutableArray *arrayPagingRange = [NSMutableArray array];
        
        CFAttributedStringRef cfStrRef = (__bridge CFAttributedStringRef)str;
        CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString(cfStrRef);
        CGPathRef path = CGPathCreateWithRect(textFrame, NULL);
        
        int textPos = 0;
        NSUInteger strLength = [str length];
        while (textPos < strLength)  {
            //设置路径
            CTFrameRef frame = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(textPos, 0), path, NULL);
            CFRange frameRange = CTFrameGetVisibleStringRange(frame);
            NSRange range = NSMakeRange(frameRange.location, frameRange.length);
            
            [arrayPagingRange addObject:[NSValue valueWithRange:range]];
            // [arrayPagingStr addObject:[str attributedSubstringFromRange:range]];
            //移动
            textPos += frameRange.length;
            CFRelease(frame);
        }
        CGPathRelease(path);
        CFRelease(framesetterRef);
        // return arrayPagingStr;
        return arrayPagingRange;
    }

     5.获取绘制区域的高度

    /**
     str:NSAttributedString 内容属性
      绘制的宽度
     */
    + (CGFloat)coretextContentHeight:(NSAttributedString *)str (CGFloat)width{
        //创建CTFramesetterRef
        CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)str);
        // 获得要绘制的区域高度
        CGSize restrictSize = CGSizeMake(width, CGFLOAT_MAX);
        CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetterRef, CFRangeMake(0,0), nil, restrictSize, nil);
       return coreTextSize.height;
    }

    项目笔记

    #import "BookDataTool.h"
    #import "CoreTextDataModel.h"
    
    @implementation BookDataTool
    
    /**
     CoreText 分页
     str: NSAttributedString属性字符串
     textFrame: 绘制区域
     */
    
    + (NSArray *)wkj_coreTextPaging:(NSAttributedString *)str
                           textArea:(CGRect)textFrame{
    //    textFrame.size.height = textFrame.size.height;
        
        ///分页NSAttributedString结果
        //    NSMutableArray *arrayPagingStr = [NSMutableArray array];
        ///分页NSRange结果
        NSMutableArray *arrayCoretext = [NSMutableArray array];
        
        CFAttributedStringRef cfStrRef = (__bridge CFAttributedStringRef)str;
        CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString(cfStrRef);
        CGPathRef path = CGPathCreateWithRect(textFrame, NULL);
        
        int textPos = 0;
        NSUInteger strLength = [str length];
        while (textPos < strLength)  {
            //设置路径
            CTFrameRef frame = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(textPos, 0), path, NULL);
            CFRange frameRange = CTFrameGetVisibleStringRange(frame);
            NSRange range = NSMakeRange(frameRange.location, frameRange.length);
            
    //        [arrayPagingRange addObject:[NSValue valueWithRange:range]];
            //        [arrayPagingStr addObject:[str attributedSubstringFromRange:range]];
            
            CoreTextDataModel *model = [[CoreTextDataModel alloc]init];
            model.ctFrame = frame;
            model.range = range;
            model.content = [str attributedSubstringFromRange:range];
            
            [arrayCoretext addObject:model];
            //移动
            textPos += frameRange.length;
            CFRelease(frame);
        }
        CGPathRelease(path);
        CFRelease(framesetterRef);
        //    return arrayPagingStr;
        return arrayCoretext;
    }
    
    
    
    
    
    ///获取属性字符串
    + (NSAttributedString *)wkj_loadTemplateFile:(NSString *)path{
        NSMutableAttributedString *resultAtt = [[NSMutableAttributedString alloc] init];
        NSData *data = [NSData dataWithContentsOfFile:path];
        if (data) {
            NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
            if ([array isKindOfClass:[NSArray class]]) {
                for (NSDictionary *dic in array) {
                    if ([dic[@"type"] isEqualToString:@"txt"]) {
                        NSAttributedString *att = [self parseAttributedContentFromDictionary:dic];
                        [resultAtt appendAttributedString:att];
                    }
                }
            }
        }
        return resultAtt;
    }
    ///获取属性字符串
    + (NSAttributedString *)parseAttributedContentFromDictionary:(NSDictionary *)dict{
        NSMutableDictionary *attributes = [self attributes];
        if ([dict[@"type1"] isEqualToString:@"0"]) {
            CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", 35, NULL);
            attributes[(id)kCTFontAttributeName] = (__bridge id)fontRef;
            CFRelease(fontRef);
        }
        NSString *content = dict[@"content"];
        return [[NSAttributedString alloc] initWithString:content attributes:attributes];
    }
    ///获取属性字符串字典
    + (NSMutableDictionary *)attributes{
        CGFloat fontSize = [BookThemeManager sharedManager].fontSize;
        CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
        CGFloat lineSpacing = [BookThemeManager sharedManager].lineSpace;
        const CFIndex kNumberOfSettings = 3;
        CTParagraphStyleSetting theSettings[kNumberOfSettings] = {
            { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing },
            { kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpacing },
            { kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpacing }
        };
        
        CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings);
        
        UIColor * textColor = [BookThemeManager sharedManager].textColor;
        
        NSMutableDictionary * dict = [NSMutableDictionary dictionary];
        dict[(id)kCTForegroundColorAttributeName] = (id)textColor.CGColor;
        dict[(id)kCTFontAttributeName] = (__bridge id)fontRef;
        dict[(id)kCTParagraphStyleAttributeName] = (__bridge id)theParagraphRef;
        CFRelease(theParagraphRef);
        CFRelease(fontRef);
        return dict;
    }
    
    @end
  • 相关阅读:
    html5 历史管理
    html5小知识点
    html5的Form新特性
    html5语义化标签
    Comet反向ajax技术实现客服聊天系统
    Js类的静态方法与实例方法区分以及jQuery如何拓展两种方法
    浏览器中关于事件的那点事儿
    iOS 强制横竖屏方法 -
    编辑readme 文件 -
    iOS- FFmpeg库的编译
  • 原文地址:https://www.cnblogs.com/wangkejia/p/8321102.html
Copyright © 2020-2023  润新知