CTFramesetterSuggestFrameSizeWithConstraints与裁剪属性返回零高度 [英] CTFramesetterSuggestFrameSizeWithConstraints with clipping attributes returning zero height

查看:308
本文介绍了CTFramesetterSuggestFrameSizeWithConstraints与裁剪属性返回零高度的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

H!
我编写一个带有子视图剪切功能的te​​xtview。想法是让所有文本绘制所有子视图。问题是得到它的内容高度。



由于缺少文档,我决定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 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 attributString =(__bridge CFAttributedStringRef)self.attributedString;
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString(attributString);

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);

}



/ p>


这正是



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

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

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;

}



输出如下所示:

 核心文字演示[2222:a0b]  -  [DATextView contentSizeForWidth:]:size = {729.71484,0} 

有人有任何解决方案吗?感谢您的关注:)

解决方案

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



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





您应该打开这些问题的错误报告,以及请求文档增强,请访问: 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:在这里做。只是举例。你应该在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:此时用户可能已设置内容插入,因此需要考虑。
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:如果有内容插入,需要补偿。
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:根据内容移动使用的矩形。
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)



>



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


H! I'm writing a textview with subviews clipping features. The idea is to make all text draw around all subviews. The problem is to get it's content height.

Due to the lack of documentation, I decided that the attributes dictionary for CTFramesetterSuggestFrameSizeWithConstraints is the same as for the CTFramesetterCreateFrame.

Here is my clipping paths code:

-(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;}

I use it to draw text and it works ok. Here is my drawing code:

- (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);

}

That's the result:

That is exactly what I was expecting!

Finally, here is the code to check the height:

-(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;

}

Output looks like this:

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

Does someone have any solution? Thanks for attention:)

解决方案

This seems to me like an iOS7 bug. I have been tinkering around, and under iOS6, CTFramesetterSuggestFrameSizeWithConstraints returns a size with height larger than 0. Same code under iOS7 returns a height of 0.

CTFramesetterSuggestFrameSizeWithConstraints is known for its buggy and undocumented behavior. For example, your code under iOS6 returns an incorrect height due to CTFramesetterSuggestFrameSizeWithConstraints performing an incorrect calculation, and the last line is omitted from the calculation. Here is your code running under iOS6:

You should open a bug report for these issues, as well as request documentation enhancements, at: https://bugreport.apple.com

Under iOS7, with TextKit, you can achieve the same result much quicker and more elegantly:

@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

And here is the result with contentInset set to UIEdgeInsetsMake(20, 200, 0, 35):

I used your code for drawing the green rectangle around the text.

这篇关于CTFramesetterSuggestFrameSizeWithConstraints与裁剪属性返回零高度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆