在iOS上调整CATextLayer大小以适应文本

23

到目前为止,我的所有研究似乎表明无法准确地实现这一点。一开始我只有两个选择:

a)对于CATextLayer使用布局管理器 - 在iOS 4.0及以上版本中不可用

b)使用sizeWithFont:constrainedToSize:lineBreakMode:并根据此处返回的大小调整CATextLayer的框架。

选项(b)是最简单的方法,应该可以奏效。毕竟,它在UILabel中完美运行。但是,当我将相同的框架计算应用于CATextLayer时,框架总是比预期或所需的略微大些。

事实证明,CATextLayers和UILabels的行间距(对于相同的字体和大小)是不同的。因此,sizeWithFont(其行间距计算会与UILabels匹配)对于CATextLayers不返回预期的大小。

通过使用UILabel打印相同的文本,并将其与使用CATextLayer打印的结果进行比较,可以进一步证明这一点。第一行的文本完全重叠(因为它是相同的字体),但CATextLayer中的行间距比UILabel中的略微短一些。(很抱歉我现在无法上传屏幕截图,因为我已经拥有的那些包含机密数据,而且我现在没有时间制作一个样本项目以获取干净的屏幕截图。等我有时间时我会上传它们以备后人参考)

这是一个奇怪的差异,但我认为可以通过为我在那里使用的NSAttributedString指定适当的属性来调整CATextLayer中的间距,但事实并非如此。查看CFStringAttributes.h后,我找不到任何可能与行间距有关的属性。

底线:

因此,在需要将层适合其文本的情况下,在iOS上似乎无法使用CATextLayer。我的理解正确吗,还是我遗漏了什么?

P.S:

  1. 我希望使用CATextLayer和NSAttributedString是因为要在不同的位置上显示不同颜色的字符串。我想我需要像以前一样手动绘制字符串...当然也可以通过hack sizeWithFont的结果来获取正确的行高。

  2. 稍微滥用了“code”标签,以使文章更易读。

  3. 我无法使用“CATextLayer”标记此文章-令人惊讶的是目前没有这样的标记。如果有足够声誉的人遇到这篇文章,请相应地标记它。


我刚遇到了这个问题...争分夺秒地寻找答案。 - griotspeak
@所有人,如果不清楚的话,这个问题严格与垂直调整固定宽度的CATextLayer有关,适用于不同长度/属性的字符串。 - Dev Kanchen
4个回答

19

试试这个:

- (CGFloat)boundingHeightForWidth:(CGFloat)inWidth withAttributedString:(NSAttributedString *)attributedString {
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString( (CFMutableAttributedStringRef) attributedString); 
    CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(inWidth, CGFLOAT_MAX), NULL);
    CFRelease(framesetter);
    return suggestedSize.height;
}

你需要将NSString转换为NSAttributedString。在使用CATextLayer的情况下,你可以使用以下CATextLayer子类方法:

- (NSAttributedString *)attributedString {

    // If string is an attributed string
    if ([self.string isKindOfClass:[NSAttributedString class]]) {
        return self.string;
    }

    // Collect required parameters, and construct an attributed string
    NSString *string = self.string;
    CGColorRef color = self.foregroundColor;
    CTFontRef theFont = self.font;
    CTTextAlignment alignment;

    if ([self.alignmentMode isEqualToString:kCAAlignmentLeft]) {
        alignment = kCTLeftTextAlignment;

    } else if ([self.alignmentMode isEqualToString:kCAAlignmentRight]) {
        alignment = kCTRightTextAlignment;

    } else if ([self.alignmentMode isEqualToString:kCAAlignmentCenter]) {
        alignment = kCTCenterTextAlignment;

    } else if ([self.alignmentMode isEqualToString:kCAAlignmentJustified]) {
        alignment = kCTJustifiedTextAlignment;

    } else if ([self.alignmentMode isEqualToString:kCAAlignmentNatural]) {
        alignment = kCTNaturalTextAlignment;
    }

    // Process the information to get an attributed string
    CFMutableAttributedStringRef attrString = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);

    if (string != nil)
        CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0), (CFStringRef)string);

    CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTForegroundColorAttributeName, color);
    CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTFontAttributeName, theFont);

    CTParagraphStyleSetting settings[] = {kCTParagraphStyleSpecifierAlignment, sizeof(alignment), &alignment};
    CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settings, sizeof(settings) / sizeof(settings[0]));
    CFAttributedStringSetAttribute(attrString, CFRangeMake(0, CFAttributedStringGetLength(attrString)), kCTParagraphStyleAttributeName, paragraphStyle);    
    CFRelease(paragraphStyle);

    NSMutableAttributedString *ret = (NSMutableAttributedString *)attrString;

    return [ret autorelease];
}

祝好。


抱歉,已经有一段时间了,我目前没有代码或时间来测试这个。不过我一定会在有时间的时候回来检查。与此同时,如果其他人看到这个答案并能够验证它的有效性,我会将其标记为“已回答”。 - Dev Kanchen
经过6个月,我终于再次遇到了这个问题。 :) 解决方案很有效。非常感谢Mustafa!! - Dev Kanchen

2
我有一个更简单的解决方案,可能适用于您。如果您没有做一些特殊的东西,例如使用CATextLayer所能做的而UILabel无法做到的,那么可以使用CALayer并将UILabel的图层添加到CALayer中。
UILabel*label = [[UILabel alloc]init];

//Do Stuff to label

CALayer *layer = [CALayer layer];

//Set Size/Position

[layer addSublayer:label.layer];

//Do more stuff to layer

啊,我明白了。就像我问题底部所提到的,我必须使用CATextLayers来利用它们的格式化能力,否则UILabel本可以完美地完成工作。不管怎样,自从发布这个问题以来已经过去很长时间了,到目前为止唯一有些希望的线索是@Mustafa的建议。遗憾的是,我没有在这么长时间里尝试过那个方法。有人要试试吗? - Dev Kanchen

0

使用LabelKit,您不再需要CATextLayer。不会再出现错误的行距和更宽的字符,所有内容都以与UILabel相同的方式绘制,仍可进行动画处理。


-2

这个页面给了我足够的信息来创建一个简单的水平居中的CATextLayer: http://lists.apple.com/archives/quartz-dev/2008/Aug/msg00016.html

- (void)drawInContext:(CGContextRef)ctx {
  CGFloat height, fontSize;

  height = self.bounds.size.height;
  fontSize = self.fontSize;

  CGContextSaveGState(ctx);
  CGContextTranslateCTM(ctx, 0.0, (fontSize-height)/2.0 * -1.0);
  [super drawInContext:ctx];
  CGContextRestoreGState(ctx);
}

抱歉,我认为这并没有回答问题。问题是关于垂直调整CATextLayer的大小以适应任何字符串,而答案是针对垂直对齐 - 还是单行文本。 - Dev Kanchen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接