xcode CTFramesetterSuggestFrameSizeWithConstraints 有时会返回不正确的大小?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3374591/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 19:34:59  来源:igfitidea点击:

CTFramesetterSuggestFrameSizeWithConstraints sometimes returns incorrect size?

iphonexcodecore-graphicscore-text

提问by carloe

In the code below, CTFramesetterSuggestFrameSizeWithConstraintssometimes returns a CGSizewith a height that is not big enough to contain all the text that is being passed into it. I did look at this answer. But in my case the width of the text box needs to be constant. Is there any other/better way to figure out the correct height for my attributed string? Thanks!

在下面的代码中,CTFramesetterSuggestFrameSizeWithConstraints有时会返回一个CGSize高度不足以包含传入其中的所有文本的高度。我确实看过这个答案。但在我的情况下,文本框的宽度需要保持不变。有没有其他/更好的方法来确定我的属性字符串的正确高度?谢谢!

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedString);
CGSize tmpSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), NULL, CGSizeMake(self.view.bounds.size.width, CGFLOAT_MAX), NULL); 
CGSize textBoxSize = CGSizeMake((int)tmpSize.width + 1, (int)tmpSize.height + 1);

回答by Chris DeSalvo

CTFramesetterSuggestFrameSizeWithConstraints works correctly. The reason that you get a height that is too short is because of the leading in the default paragraph style attached to attributed strings. If you don't attach a paragraph style to the string then CoreText returns the height needed to render the text, but with no space between the lines. This took me forever to figure out. Nothing in the documentation spells it out. I just happened to notice that my heights were short by an amount equal to (number of lines x expected leading). To get the height result you expect you can use code like the following:

CTFramesetterSuggestFrameSizeWithConstraints 工作正常。您得到的高度太短的原因是附加到属性字符串的默认段落样式中的前导。如果您没有将段落样式附加到字符串,那么 CoreText 将返回呈现文本所需的高度,但行之间没有空格。这让我花了很长时间才弄明白。文档中的任何内容都没有说明。我只是碰巧注意到我的高度短了一个等于(行数 x 预期领先的数量)。要获得您期望的高度结果,您可以使用如下代码:

NSString  *text = @"This\nis\nsome\nmulti-line\nsample\ntext."
UIFont    *uiFont = [UIFont fontWithName:@"Helvetica" size:17.0];
CTFontRef ctFont = CTFontCreateWithName((CFStringRef) uiFont.fontName, uiFont.pointSize, NULL);

//  When you create an attributed string the default paragraph style has a leading 
//  of 0.0. Create a paragraph style that will set the line adjustment equal to
//  the leading value of the font.
CGFloat leading = uiFont.lineHeight - uiFont.ascender + uiFont.descender;
CTParagraphStyleSetting paragraphSettings[1] = { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &leading };

CTParagraphStyleRef  paragraphStyle = CTParagraphStyleCreate(paragraphSettings, 1);
CFRange textRange = CFRangeMake(0, text.length);

//  Create an empty mutable string big enough to hold our test
CFMutableAttributedStringRef string = CFAttributedStringCreateMutable(kCFAllocatorDefault, text.length);

//  Inject our text into it
CFAttributedStringReplaceString(string, CFRangeMake(0, 0), (CFStringRef) text);

//  Apply our font and line spacing attributes over the span
CFAttributedStringSetAttribute(string, textRange, kCTFontAttributeName, ctFont);
CFAttributedStringSetAttribute(string, textRange, kCTParagraphStyleAttributeName, paragraphStyle);

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(string);
CFRange fitRange;

CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, textRange, NULL, bounds, &fitRange);

CFRelease(framesetter);
CFRelease(string);

回答by Lily Ballard

CTFramesetterSuggestFrameSizeWithConstraints()is broken. I filed a bug on this a while back. Your alternative is to use CTFramesetterCreateFrame()with a path that is sufficiently high. Then you can measure the rect of the CTFrame that you get back. Note that you cannot use CGFLOAT_MAXfor the height, as CoreText uses a flipped coordinate system from the iPhone and will locate its text at the "top" of the box. This means that if you use CGFLOAT_MAX, you won't have enough precision to actually tell the height of the box. I recommend using something like 10,000 as your height, as that's 10x taller than the screen itself and yet gives enough precision for the resulting rectangle. If you need to lay out even taller text, you can do this multiple times for each section of text (you can ask CTFrameRef for the range in the original string that it was able to lay out).

CTFramesetterSuggestFrameSizeWithConstraints()被打破。不久前我提交了一个关于此的错误。您的替代方法是使用CTFramesetterCreateFrame()足够高的路径。然后您可以测量返回的 CTFrame 的矩形。请注意,您不能使用CGFLOAT_MAX高度,因为 CoreText 使用来自 iPhone 的翻转坐标系,并将其文本定位在框的“顶部”。这意味着如果你使用CGFLOAT_MAX,你将没有足够的精度来实际告诉盒子的高度。我建议使用 10,000 之类的东西作为您的高度,因为它比屏幕本身高 10 倍,但仍能为生成的矩形提供足够的精度。如果您需要布置更高的文本,您可以对每个文本部分执行多次(您可以向 CTFrameRef 询问它能够布置的原始字符串中的范围)。

回答by George

Thanks to Chris DeSalvo for the excellent answer! Finally ending 16 hours of debugging. I had a little trouble figuring out the Swift 3 syntax. So sharing the Swift 3 version of setting the paragraph style.

感谢 Chris DeSalvo 的出色回答!终于结束了16个小时的调试。我在弄清楚 Swift 3 语法时遇到了一些麻烦。所以分享Swift 3版本设置段落样式。

let leading = uiFont.lineHeight - uiFont.ascender + uiFont.descender
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = leading      
mutableAttributedString.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: textRange)

回答by Reinier Melian

Working on this problem and following a lot of different answers from several posters, I had implemented a solution for the all mighty problem of correct text size, for me CTFramesetterSuggestFrameSizeWithConstraintsis not working properly so we need to use CTFramesetterCreateFrameand then measure the size for that frame, (this is a UIFontextension) this is swift 100%

解决这个问题并遵循几张海报的许多不同答案,我已经为正确文本大小的所有强大问题实施了一个解决方案,因为我CTFramesetterSuggestFrameSizeWithConstraints无法正常工作所以我们需要使用CTFramesetterCreateFrame然后测量该框架的大小, (这是一个UIFont扩展)这是100% 的 swift

References

参考

CTFramesetterSuggestFrameSizeWithConstraints sometimes returns incorrect size?

CTFramesetterSuggestFrameSizeWithConstraints 有时会返回不正确的大小?

How to get the real height of text drawn on a CTFrame

如何获取在 CTFrame 上绘制的文本的真实高度

Using CFArrayGetValueAtIndex in Swift with UnsafePointer (AUPreset)

在 Swift 中使用 CFArrayGetValueAtIndex 和 UnsafePointer (AUPreset)

func sizeOfString (string: String, constrainedToWidth width: Double) -> CGSize {
        let attributes = [NSAttributedString.Key.font:self]
        let attString = NSAttributedString(string: string,attributes: attributes)
        let framesetter = CTFramesetterCreateWithAttributedString(attString)

        let frame = CTFramesetterCreateFrame(framesetter,CFRange(location: 0,length: 0),CGPath.init(rect: CGRect(x: 0, y: 0, width: width, height: 10000), transform: nil),nil)

        return UIFont.measure(frame: frame)
    }

Then we measure our CTFrame

然后我们测量我们的 CTFrame

static func measure(frame:CTFrame) ->CGSize {

        let lines = CTFrameGetLines(frame)
        let numOflines = CFArrayGetCount(lines)
        var maxWidth : Double = 0

        for index in 0..<numOflines {
            let line : CTLine = unsafeBitCast(CFArrayGetValueAtIndex(lines, index), to: CTLine.self)
            var ascent : CGFloat = 0
            var descent : CGFloat = 0
            var leading : CGFloat = 0
            let width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading)

            if(width > maxWidth) {
                maxWidth = width
            }
        }

        var ascent : CGFloat = 0
        var descent : CGFloat = 0
        var leading : CGFloat = 0


        CTLineGetTypographicBounds(unsafeBitCast(CFArrayGetValueAtIndex(lines, 0), to: CTLine.self), &ascent, &descent, &leading)
        let firstLineHeight = ascent + descent + leading

        CTLineGetTypographicBounds(unsafeBitCast(CFArrayGetValueAtIndex(lines, numOflines - 1), to: CTLine.self), &ascent, &descent, &leading)
        let lastLineHeight = ascent + descent + leading

        var firstLineOrigin : CGPoint = CGPoint(x: 0, y: 0)
        CTFrameGetLineOrigins(frame, CFRangeMake(0, 1), &firstLineOrigin);

        var lastLineOrigin : CGPoint = CGPoint(x: 0, y: 0)
        CTFrameGetLineOrigins(frame, CFRangeMake(numOflines - 1, 1), &lastLineOrigin);

        let textHeight = abs(firstLineOrigin.y - lastLineOrigin.y) + firstLineHeight + lastLineHeight

        return CGSize(width: maxWidth, height: Double(textHeight))
    }

回答by Matt.Z

I finally find out.. When uses CTFramesetterSuggestFrameSizeWithConstraints returning CGSize to draw text, the size is considered not big enough(sometimes) for the whole text, so last line is not drew.. I just add 1 to size.height for return, it appears to be right now.

我终于发现了.. 当使用 CTFramesetterSuggestFrameSizeWithConstraints 返回 CGSize 绘制文本时,大小被认为不够大(有时)整个文本,所以最后一行没有绘制.. 我只是在 size.height 上加 1 返回,它似乎是现在。

Something like this:

像这样的东西:

suggestedSize = CGSizeMake(suggestedSize.width, suggestedSize.height + 1);

建议大小 = CGSizeMake(建议大小.宽度,建议大小.高度 + 1);