【问题标题】:CTFramesetterSuggestFrameSizeWithConstraints sometimes returns incorrect size?CTFramesetterSuggestFrameSizeWithConstraints 有时会返回不正确的大小?
【发布时间】:2011-03-23 10:07:02
【问题描述】:

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

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

【问题讨论】:

    标签: iphone xcode core-graphics core-text


    【解决方案1】:

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

    【讨论】:

    • 那么当属性字符串中有多种字体时,如何做到这一点?
    • 您可以使用CFAttributedStringSetAttributes将段落属性添加到字符串中,并为clearOtherAttributes传递NO
    • 它对我来说仍然太短,即使使用 Chris 的代码。
    • 感谢您提供的优质信息。你应该释放“paragraphStyle”。
    • 有时这种方法对我来说失败了,高度太短了。
    【解决方案2】:

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

    【讨论】:

    • CGFLOAT_MAX 在当前情况下确实是一个问题,将值设置为 100.000 似乎效果很好。奇怪的是我只在iOS5上遇到这个问题。
    • @GregoryM:是的,这就是为什么我建议使用 10,000 之类的值,这是我使用的值(大到有用,小到仍然允许小数部分有足够的精度)。
    • 这是一个旧答案,但是如何从 CTFrame 中获取 CGRect 或高度?
    • @AndrewPark:这有点复杂。您必须计算框架中最后一行的矩形,并取其底部与您给框架设置器的空间顶部之间的差异,以找出它需要多少空间。
    • @KevinBallard 感谢您重新审视这个问题和澄清!
    【解决方案3】:

    感谢 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)
    

    【讨论】:

      【解决方案4】:

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

      参考文献

      CTFramesetterSuggestFrameSizeWithConstraints sometimes returns incorrect size?

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

      Using CFArrayGetValueAtIndex in Swift with 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)
          }
      

      然后我们测量我们的 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))
          }
      

      【讨论】:

        【解决方案5】:

        这让我很生气,上面的解决方案都没有让我计算多页上长字符串的布局并返回带有完全截断行的范围值。阅读 API 说明后,CTFramesetterSuggestFrameSizeWithConstraints 中 'stringRange' 的参数如下:

        '帧大小将应用到的字符串范围。字符串范围是用于创建框架设置器的字符串的范围。如果范围的长度部分设置为 0,则框架设置器将继续添加行,直到用完文本或空间。'

        将字符串范围设置为 'CFRangeMake(currentLocation, 0)' 而不是 'CFRangeMake(currentLocation, string.length)' 后,一切正常。

        【讨论】:

          【解决方案6】:

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

          类似这样的:

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

          【讨论】:

            猜你喜欢
            • 2011-02-12
            • 1970-01-01
            • 1970-01-01
            • 2020-11-06
            • 2023-03-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-03-01
            相关资源
            最近更新 更多