CoreText 开发中遇到的小问题

2018-03-09  本文已影响0人  剁椒鸡蛋zy

1. CoreText 中CTRunGetPosition 函数的小坑

下面代码为例


7283612d-a27a-45e9-b769-1b7d513ad3f5-134466.jpg
  1. for循环每次执行到第二个循环的时候,肯定会在NSlog 那里挂掉?
    因为 CTRunGetPosition中第二个参数range,如果range.length == 0的话,是去获取当前CTRun里面每个字形的position,所有第三个参数需要传递的是一个数组。
  2. CTRunGetPosition这个函数返回的字形的position是相对当前其所在的CTLine的,position.y === 0 position.x = 到line左边的距离

2. UIView的 drawRect:方法

看下面简单的代码

- (void)drawRect:(CGRect)rect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    int s = random() % 2;
    if (s == 1) {
        CGContextSaveGState(ctx);
        CGPathRef path = CGPathCreateWithRect(CGRectMake(10, 40, 50, 30), nil);
        CGContextAddPath(ctx, path);
        CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
        CGContextFillPath(ctx);
        CGContextRestoreGState(ctx);
    }else{
        CGContextSaveGState(ctx);
        CGPathRef path = CGPathCreateWithRect(CGRectMake(100, 100, 50, 30), nil);
        CGContextAddPath(ctx, path);
        CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor);
        CGContextFillPath(ctx);
        CGContextRestoreGState(ctx);
    }
}

当我们想让 UIView 去重新绘制界面的时候,会去 调用 setNeedsDisplay 方法,然后系统会去调用 drawRect 绘制,这种情况下回清空以前的内容,重新开始绘制; 但是当调用 setNeedsDisplayInRect: 的时候 并且rect 不等于 UIVIewbounds 的时候,不会清空以前的内容,并且在传入的rect 内重新绘制。

3.CoreText 中frame代表什么 段落间距到底如何设置分析?

 NSMutableAttributedString *atrriStr = [[NSMutableAttributedString alloc] initWithString:@"一二三四五六七八九十一二三四五六七八九十一二三四567890\n123五六七八九十一二三四五六七八九十一二三四五六七八九十gh"];
 CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)atrriStr);
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL,rect);
    int offset = 28;
    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, offset), path,nil);
    CTFrameRef frame2 =  CTFramesetterCreateFrame(frameSetter, CFRangeMake(offset, atrriStr.length - offset), path, nil);

上面的代码 生成的结果如下


26233274-8B83-430B-BFFD-3F428AD9D3F8.png

可以看到90竟然跑到上面去了,可以总结出:
1. 一个长的string 生成多个frame 然后每个frame 都调用CTFrameDraw(frame, ctx);去绘制的时候每个frame都是从容器的原点开始绘制的,并不会去应用段落间距样式,CoreText内部还是以换行符为段落分隔符,并不是每个CTFrame就是一个段落。

测试代码地址
CoreText段落样式

4. CoreText关于内存释放的问题

@interface TextLine : NSObject
+ (instancetype)lineWithCTLine:(CTLineRef)ctline position:(CGPoint)position;
@property (nonatomic,readonly) CTLineRef ctLine;
@end
/**
 处理line 里面的attachment
 */
- (void)proceeAttachment{
    if (!_ctLine) {
        return;
    }
    self.runRectAry = [NSMutableArray array];
    self.runStrRangeAry = [NSMutableArray array];
    // 遍历line里面的runs,看看那个run有runDelegate,有就表示这个run是图片,然后保存起来
    CFArrayRef runs = CTLineGetGlyphRuns(_ctLine);
    NSInteger runCount = CFArrayGetCount(runs);
    for (int i = 0; i < runCount; i++) {
        CTRunRef run = CFArrayGetValueAtIndex(runs, i);
        CFRange strRange = CTRunGetStringRange(run);
        NSValue *rangeValue = [NSValue valueWithRange:NSMakeRange(strRange.location, strRange.length)];
        [self.runStrRangeAry addObject:rangeValue];
        // ---  下面这段代码把每个run的rect保存起来
        CFIndex glyphCount = CTRunGetGlyphCount(run);
        if (glyphCount == 0) {
            [self.runRectAry addObject:[NSValue valueWithCGRect:CGRectZero]];
        }else{
            CGPoint runPos;
            CTRunGetPositions(run, CFRangeMake(0, 1), &runPos);
            CGFloat ascent;
            CGFloat descent;
            CGFloat leading;
            CGFloat runWidth;
            runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading);
            CGRect rect = CGRectMake(runPos.x, runPos.y, runWidth, ascent + descent);
            NSValue *v = [NSValue valueWithCGRect:rect];
            if (v) {
                [self.runRectAry addObject:v];
            }
        }
        // ---
        CFDictionaryRef runAttri = CTRunGetAttributes(run);
        CTRunDelegateRef runDelegate = CFDictionaryGetValue(runAttri, kCTRunDelegateAttributeName);
//        FuTextRun *attachment = (__bridge FuTextRun *)CTRunDelegateGetRefCon(runDelegate);
        GWWTextAttachmentCallbackModel *model = (__bridge GWWTextAttachmentCallbackModel *)CTRunDelegateGetRefCon(runDelegate);
        if (!model || model.type == GWWTextAttachmentTypeBlack) {
            continue;
        }
        CGPoint runPoint; // 这个point 是相对于当前line里面计算的
        CTRunGetPositions(run, CFRangeMake(0, 0), &runPoint);
        // 计算附件的位置信息,如果附件图片比当前行小,就居中,大就底部对齐
        GWWTextAttachment *txtAttach = [[GWWTextAttachment alloc] init];
        txtAttach.srcUrl = model.originalString;
        txtAttach.rect =  (CGRect){runPoint,model.size};
        txtAttach.position = runPoint;
        CFRange range = CTRunGetStringRange(run);
        txtAttach.rangeOfOriginString = NSMakeRange(range.location, range.length);
        // 谨记 这个不能释放,因为runAttri 取到的数据是包含在run里面的导致以后释放line的时候遇到
        // [CFDictionary release]: message sent to deallocated instance 0x1c806fa80
        // 问题
//        CFRelease(runAttri);
//        CFRelease(runDelegate);
        [self.attachmentAry addObject:txtAttach];
    }
}

- (void)dealloc{
// 对象释放的时候要记得释放ctline
    if (_ctLine) {
      CFRelease(_ctLine);
    }
}

还有设置attributeString的属性样式的时候也要记得release

    CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(setting, sizeof(setting) / sizeof(CTParagraphStyleSetting));
    [attributes setObject:(__bridge id)paragraphStyle forKey:(id)kCTParagraphStyleAttributeName];
    CFRelease(paragraphStyle);

 CTRunDelegateRef runDelegate = CTRunDelegateCreate(&callbacks, (__bridge_retained void *)model);
    [string addAttributes:@{(id)kCTRunDelegateAttributeName: (__bridge id)runDelegate} range:NSMakeRange(0, string.length)];
    CFRelease(runDelegate);
para泄漏 2.png
上一篇下一篇

猜你喜欢

热点阅读