NSAttributedString 高度受宽度和 numberOfLines 限制
NSAttributedString height limited by width and numberOfLines
我需要计算自定义标签中的文本矩形,而不是使用 UILabel 的 sizeThatFits 方法。下面的代码无法正常工作。主要思想是在 index = numberOfLines - 1 和 return 它的最大 y 位置找到 CTLine。但结果文本高度有时太大,有时不足以绘制最后一行。
- (CGSize)fittingSizeWithSize:(CGSize)size numberOfLines:(NSInteger)numberOfLines {
if (numberOfLines == 0) {
return [self fittingSizeWithSize:size];
}
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self);
if (framesetter == NULL) {
return CGSizeZero;
}
CGPathRef path = CGPathCreateWithRect(CGRectMake(0,0,size.width,size.height), NULL);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, self.length), path, NULL);
NSArray *lines = (NSArray *) CTFrameGetLines(frame);
if (lines.count == 0) {
return CGSizeZero;
}
NSUInteger lineIndex = MIN((NSUInteger)numberOfLines, lines.count) - 1;
CTLineRef line = (__bridge CTLineRef) lines[lineIndex];
CGPoint origins[[lines count]];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
CGAffineTransform transform = CGAffineTransformMakeTranslation(0, size.height);
transform = CGAffineTransformScale(transform, 1, -1);
CGRect lineRect;
CGFloat ascent;
CGFloat descent;
lineRect.size.width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, NULL); //8
lineRect.size.height = ascent + descent;
lineRect.origin.y = CGPointApplyAffineTransform(origins[lineIndex], transform).y;
CGFloat height = CGRectGetMaxY(lineRect);
CFRelease(path);
CFRelease(framesetter);
return CGSizeMake(size.width, height);
}
我的 UILabel 子类中使用了这个类的 NSAttributedString
@implementation SMBDLabel
- (void)drawTextInRect:(CGRect)rect {
if (self.attributedText) {
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self.attributedText drawInContext:ctx viewBounds:rect];
} else {
[super drawTextInRect:rect];
}
}
- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines {
CGSize size = [self.attributedText fittingSizeWithSize:bounds.size numberOfLines:numberOfLines];
return CGRectMake(0, 0, size.width, size.height);
}
- (CGSize)sizeThatFits:(CGSize)size {
return [self.attributedText fittingSizeWithSize:size numberOfLines:self.numberOfLines];
}
@end
我不知道我的错误在哪里。也许实际上在 UILabel 子类中出错
解决方案似乎是最简单的。无需获取行起点和排版边界。具有文本特定文本范围的 CTFramesetterSuggestFrameSizeWithConstraints 将完成所有工作
- (CGSize)fittingSizeWithSize:(CGSize)size numberOfLines:(NSInteger)numberOfLines {
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self);
if (!framesetter) {
return CGSizeZero;
}
if (numberOfLines == 0) {
CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), NULL, size, NULL);
if (framesetter != NULL) {
CFRelease(framesetter);
}
return CGSizeMake(ceilf(textSize.width), ceilf(textSize.height));
} else {
CGPathRef path = CGPathCreateWithRect(CGRectMake(0, 0, size.width, CGFLOAT_MAX), NULL);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, self.length), path, NULL);
if (path != NULL) {
CFRelease(path);
}
NSArray *lines = (NSArray *)CTFrameGetLines(frame);
__block CFIndex len = 0;
[lines enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (numberOfLines > 0 && idx == numberOfLines) {
*stop = YES;
return;
}
CTLineRef line = (__bridge CTLineRef)obj;
CFRange range = CTLineGetStringRange(line);
len += range.length;
}];
CFRange strRange = CFRangeMake(0, len);
CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, strRange, NULL, size, NULL);
if (framesetter != NULL) {
CFRelease(framesetter);
}
return CGSizeMake(ceilf(textSize.width), ceilf(textSize.height));
}
}
我需要计算自定义标签中的文本矩形,而不是使用 UILabel 的 sizeThatFits 方法。下面的代码无法正常工作。主要思想是在 index = numberOfLines - 1 和 return 它的最大 y 位置找到 CTLine。但结果文本高度有时太大,有时不足以绘制最后一行。
- (CGSize)fittingSizeWithSize:(CGSize)size numberOfLines:(NSInteger)numberOfLines {
if (numberOfLines == 0) {
return [self fittingSizeWithSize:size];
}
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self);
if (framesetter == NULL) {
return CGSizeZero;
}
CGPathRef path = CGPathCreateWithRect(CGRectMake(0,0,size.width,size.height), NULL);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, self.length), path, NULL);
NSArray *lines = (NSArray *) CTFrameGetLines(frame);
if (lines.count == 0) {
return CGSizeZero;
}
NSUInteger lineIndex = MIN((NSUInteger)numberOfLines, lines.count) - 1;
CTLineRef line = (__bridge CTLineRef) lines[lineIndex];
CGPoint origins[[lines count]];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), origins);
CGAffineTransform transform = CGAffineTransformMakeTranslation(0, size.height);
transform = CGAffineTransformScale(transform, 1, -1);
CGRect lineRect;
CGFloat ascent;
CGFloat descent;
lineRect.size.width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, NULL); //8
lineRect.size.height = ascent + descent;
lineRect.origin.y = CGPointApplyAffineTransform(origins[lineIndex], transform).y;
CGFloat height = CGRectGetMaxY(lineRect);
CFRelease(path);
CFRelease(framesetter);
return CGSizeMake(size.width, height);
}
我的 UILabel 子类中使用了这个类的 NSAttributedString
@implementation SMBDLabel
- (void)drawTextInRect:(CGRect)rect {
if (self.attributedText) {
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self.attributedText drawInContext:ctx viewBounds:rect];
} else {
[super drawTextInRect:rect];
}
}
- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines {
CGSize size = [self.attributedText fittingSizeWithSize:bounds.size numberOfLines:numberOfLines];
return CGRectMake(0, 0, size.width, size.height);
}
- (CGSize)sizeThatFits:(CGSize)size {
return [self.attributedText fittingSizeWithSize:size numberOfLines:self.numberOfLines];
}
@end
我不知道我的错误在哪里。也许实际上在 UILabel 子类中出错
解决方案似乎是最简单的。无需获取行起点和排版边界。具有文本特定文本范围的 CTFramesetterSuggestFrameSizeWithConstraints 将完成所有工作
- (CGSize)fittingSizeWithSize:(CGSize)size numberOfLines:(NSInteger)numberOfLines {
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self);
if (!framesetter) {
return CGSizeZero;
}
if (numberOfLines == 0) {
CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), NULL, size, NULL);
if (framesetter != NULL) {
CFRelease(framesetter);
}
return CGSizeMake(ceilf(textSize.width), ceilf(textSize.height));
} else {
CGPathRef path = CGPathCreateWithRect(CGRectMake(0, 0, size.width, CGFLOAT_MAX), NULL);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, self.length), path, NULL);
if (path != NULL) {
CFRelease(path);
}
NSArray *lines = (NSArray *)CTFrameGetLines(frame);
__block CFIndex len = 0;
[lines enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (numberOfLines > 0 && idx == numberOfLines) {
*stop = YES;
return;
}
CTLineRef line = (__bridge CTLineRef)obj;
CFRange range = CTLineGetStringRange(line);
len += range.length;
}];
CFRange strRange = CFRangeMake(0, len);
CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, strRange, NULL, size, NULL);
if (framesetter != NULL) {
CFRelease(framesetter);
}
return CGSizeMake(ceilf(textSize.width), ceilf(textSize.height));
}
}