ios - 具有返回零高度的裁剪属性的 CTFramesetterSuggestFrameSizeWithConstraints

标签 ios objective-c cocoa-touch core-text

哼! 我正在写一个带有 subview 剪辑功能的 TextView 。这个想法是让所有文本围绕所有 subview 绘制。问题是获取它的内容高度。

由于缺少文档,我决定 CTFramesetterSuggestFrameSizeWithConstraints 的属性字典与 CTFramesetterCreateFrame 的相同。

这是我的剪切路径代码:

-(CFDictionaryRef)clippingPathsDictionary{
if(self.subviews.count==0)return NULL;

NSMutableArray *pathsArray = [[NSMutableArray alloc] init];

CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformScale(transform, 1, -1);
transform = CGAffineTransformTranslate(transform, 0, -self.bounds.size.height);

for (int i=0; i<self.subviews.count; i++) {
    UIView *clippingView = self.subviews[i];
    CGPathRef clipPath = CGPathCreateWithRect(clippingView.frame, &transform);

    NSDictionary *clippingPathDictionary = [NSDictionary dictionaryWithObject:(__bridge id)(clipPath) forKey:(__bridge NSString *)kCTFramePathClippingPathAttributeName];
    [pathsArray addObject:clippingPathDictionary];
    CFRelease(clipPath);
}


int eFrameWidth=0;
CFNumberRef frameWidth = CFNumberCreate(NULL, kCFNumberNSIntegerType, &eFrameWidth);

int eFillRule = kCTFramePathFillEvenOdd;
CFNumberRef fillRule = CFNumberCreate(NULL, kCFNumberNSIntegerType, &eFillRule);

int eProgression = kCTFrameProgressionTopToBottom;
CFNumberRef progression = CFNumberCreate(NULL, kCFNumberNSIntegerType, &eProgression);


CFStringRef keys[] = { kCTFrameClippingPathsAttributeName, kCTFramePathFillRuleAttributeName, kCTFrameProgressionAttributeName, kCTFramePathWidthAttributeName};
CFTypeRef values[] = { (__bridge CFTypeRef)(pathsArray), fillRule, progression, frameWidth};




CFDictionaryRef clippingPathsDictionary = CFDictionaryCreate(NULL,
                                                             (const void **)&keys, (const void **)&values,
                                                             sizeof(keys) / sizeof(keys[0]),
                                                             &kCFTypeDictionaryKeyCallBacks,
                                                             &kCFTypeDictionaryValueCallBacks);
return clippingPathsDictionary;}

我用它来绘制文本,它工作正常。 这是我的绘图代码:

- (void)drawRect:(CGRect)rect{
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformScale(transform, 1, -1);
transform = CGAffineTransformTranslate(transform, 0, -rect.size.height);


CGContextRef context = UIGraphicsGetCurrentContext();
CGContextConcatCTM(context, transform);

CFAttributedStringRef attributedString = (__bridge CFAttributedStringRef)self.attributedString;
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(attributedString);

CFDictionaryRef  attributesDictionary = [self clippingPathsDictionary];
CGPathRef path = CGPathCreateWithRect(rect, &transform);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, self.attributedString.length), path, attributesDictionary);
CFRelease(path);
CFRelease(attributesDictionary);

CTFrameDraw(frame, context);
CFRelease(frameSetter);
CFRelease(frame);

CGSize contentSize = [self contentSizeForWidth:self.bounds.size.width];

CGPathRef highlightPath = CGPathCreateWithRect((CGRect){CGPointZero, contentSize}, &transform);
CGContextSetFillColorWithColor(context, [UIColor colorWithRed:.0 green:1 blue:.0 alpha:.3].CGColor);
CGContextAddPath(context, highlightPath);
CGContextDrawPath(context, kCGPathFill);
CFRelease(highlightPath);

结果是:

Drawing result 这正是我所期待的!

最后,这是检查高度的代码:

-(CGSize)contentSizeForWidth:(float)width{
CFAttributedStringRef attributedString = (__bridge CFAttributedStringRef)self.attributedString;
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(attributedString);

CFDictionaryRef attributesDictionary = [self clippingPathsDictionary];
CGSize size = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, CFRangeMake(0, self.attributedString.length),  attributesDictionary, CGSizeMake(width, CGFLOAT_MAX), NULL);
NSLog(@"%s: size = %@",__PRETTY_FUNCTION__, NSStringFromCGSize(size));
CFRelease(attributesDictionary);
CFRelease(frameSetter);
return size;

输出看起来像这样:

Core Text Demo[2222:a0b] -[DATextView contentSizeForWidth:]: size = {729.71484, 0}

有人有解决办法吗?感谢关注:)

最佳答案

在我看来,这像是一个 iOS7 错误。我一直在修修补补,在 iOS6 下,CTFramesetterSuggestFrameSizeWithConstraints 返回高度大于 0 的大小。在 iOS7 下相同的代码返回高度为 0。

CTFramesetterSuggestFrameSizeWithConstraints 以其错误和未记录的行为而闻名。例如,由于 CTFramesetterSuggestFrameSizeWithConstraints 执行了错误的计算,您在 iOS6 下的代码返回了错误的高度,并且计算中省略了最后一行。这是您在 iOS6 下运行的代码:

enter image description here

您应该针对这些问题打开错误报告,并请求文档增强,网址为:https://bugreport.apple.com

在 iOS7 下,使用 TextKit,您可以更快、更优雅地实现相同的结果:

@interface LeoView : UIView

@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, retain) NSLayoutManager* layoutManager;
@property (nonatomic, retain) NSTextContainer* textContainer;
@property (nonatomic, retain) NSTextStorage* textStorage;

@end

@implementation LeoView

-(void)awakeFromNib
{
    //Leo: This should not be done here. Just for example. You should do this in the init.

    self.textStorage = [[NSTextStorage alloc] initWithString:@"Lorem ipsum dolor [...]"];

    self.layoutManager = [[NSLayoutManager alloc] init];
    [self.textStorage addLayoutManager:self.layoutManager];

    self.textContainer = [[NSTextContainer alloc] initWithSize:self.bounds.size];
    [self.layoutManager addTextContainer:self.textContainer];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    //Leo: At this point the user may have set content inset, so need to take into account.
    CGSize size = self.bounds.size;
    size.width -= (self.contentInset.left + self.contentInset.right);
    size.height -= (self.contentInset.top + self.contentInset.bottom);

    self.textContainer.size = size;

    NSMutableArray* exclussionPaths = [NSMutableArray new];
    for (UIView* subview in self.subviews)
    {
        if(subview.isHidden)
            continue;

        CGRect frame = subview.frame;
        //Leo: If there is content inset, need to compensate.
        frame.origin.y -= self.contentInset.top;
        frame.origin.x -= self.contentInset.left;

        [exclussionPaths addObject:[UIBezierPath bezierPathWithRect:frame]];
    }
    self.textContainer.exclusionPaths = exclussionPaths;

    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect
{
    [self.layoutManager drawGlyphsForGlyphRange:[self.layoutManager glyphRangeForTextContainer:self.textContainer] atPoint:CGPointMake(self.contentInset.left, self.contentInset.top)];

    //Leo: Move used rectangle according to content inset.
    CGRect usedRect = [self.layoutManager usedRectForTextContainer:self.textContainer];
    usedRect.origin.x += self.contentInset.left;
    usedRect.origin.y += self.contentInset.top;

    CGAffineTransform transform = CGAffineTransformIdentity;
    transform = CGAffineTransformScale(transform, 1, -1);
    transform = CGAffineTransformTranslate(transform, 0, -rect.size.height);

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextConcatCTM(context, transform);
    CGPathRef highlightPath = CGPathCreateWithRect(usedRect, &transform);
    CGContextSetFillColorWithColor(context, [UIColor colorWithRed:.0 green:1 blue:.0 alpha:.3].CGColor);
    CGContextAddPath(context, highlightPath);
    CGContextDrawPath(context, kCGPathFill);
    CFRelease(highlightPath);
}

@end

下面是 contentInset 设置为 UIEdgeInsetsMake(20, 200, 0, 35) 的结果:

enter image description here

我使用您的代码在文本周围绘制了绿色矩形。

关于ios - 具有返回零高度的裁剪属性的 CTFramesetterSuggestFrameSizeWithConstraints,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19377421/

相关文章:

iphone - 将音频从 iPhone 传输到浏览器。有想法吗?

ios - 无法在 iOS 应用程序中使用 dispatch_source_t 在 GCD block 中运行计时器

ios - 查找特定 UICollectionView 单元格的索引号

objective-c - 带有 UILabel 的自定义 View 显示为黑色

objective-c - 我将如何使用 AFNetwork 创建一个到网络服务器的 GET 方法

iPhone - 如何将 iPhone 专用应用程序转换为通用应用程序?

ios - 在枚举时修改 NSMutableArray 中的 NSDictionary 项的正确方法是什么?

ios - 应用程序因多个视频而崩溃

iphone - 是否可以在 iPhone 上的 UIWebView 上以编程方式调用 "select all",然后调用 "copy"?

objective-c - 这个 ' ->' 在 c/objective-c 中是什么意思?