【问题标题】:Calculate Font Size to Fit Frame - Core Text - NSAttributedString - iOS计算字体大小以适合框架 - 核心文本 - NSAttributedString - iOS
【发布时间】:2013-12-26 10:41:17
【问题描述】:

我有一些文本,我正在通过 NSAttributedString(下面的代码)将它们绘制到固定框架中。目前我正在将文本大小硬编码为 16。我的问题是,有没有办法计算给定框架的文本的最佳拟合大小?

- (void)drawText:(CGContextRef)contextP startX:(float)x startY:(float)
y withText:(NSString *)standString
{
    CGContextTranslateCTM(contextP, 0, (bottom-top)*2);
    CGContextScaleCTM(contextP, 1.0, -1.0);

    CGRect frameText = CGRectMake(1, 0, (right-left)*2, (bottom-top)*2);

    NSMutableAttributedString * attrString = [[NSMutableAttributedString alloc] initWithString:standString];
    [attrString addAttribute:NSFontAttributeName
                      value:[UIFont fontWithName:@"Helvetica-Bold" size:16.0]
                      range:NSMakeRange(0, attrString.length)];

    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attrString));
    struct CGPath * p = CGPathCreateMutable();
    CGPathAddRect(p, NULL, frameText);
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0), p, NULL);

    CTFrameDraw(frame, contextP);
}

【问题讨论】:

  • 这个自定义的 UILabel 正在使用这个。我认为这可以帮助https://github.com/vigorouscoding/KSLabel
  • 我没有使用 UILabel,因为它们必须是方形的 - 这是被绘制成 Quartz 2D 创建形状的文本。
  • UILabels 可以是方形的吗?
  • @GuybrushThreepwood - 看我的回答。这确实是一个简单快速的解决方案。

标签: ios objective-c fonts core-text nsattributedstring


【解决方案1】:

下面是一段简单的代码,它将计算出适合框架边界的最大字体大小:

UILabel *label = [[UILabel alloc] initWithFrame:frame];
label.text = @"Some text";
float largestFontSize = 12;
while ([label.text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:largestFontSize]}].width > modifierFrame.size.width)
{
     largestFontSize--;
}
label.font = [UIFont systemFontOfSize:largestFontSize];

【讨论】:

  • 效果很好!除了一行文字。我想知道是否有办法做多行
  • 我知道这是一个旧帖子,但它仍然可以在今天和明天帮助一些人。所以我投入了 2 美分:要获得多行的字体大小,您可以传递行数,然后您可以这样做: ...while ([label.text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize: maximumFontSize]}].width > modifierFrame.size.width * numberOfLines)
【解决方案2】:

我认为这是可能的唯一方法是让系统运行尺寸计算,然后调整尺寸并重复,直到找到合适的尺寸。

即设置一个在特定大小之间的二等分算法。

即运行大小 10。 太小。 尺寸 20。 太小。 尺寸 30。 太大。 尺寸 25。 太小。 尺寸 27。 正好,用 27 码。

你甚至可以从数百开始。

尺寸 100。 太大。 尺寸 50。 等等……

【讨论】:

  • 谢谢 - 我想问题是我怎么知道它的尺寸是否合适。你让我重新思考——也许我想做的事情是不可能的。
  • 不,但它来自某人:-)
【解决方案3】:

一个小技巧有助于利用 sizeWithAttributes: 而无需迭代以获得正确的结果:

NSSize sampleSize = [wordString sizeWithAttributes:
    @{ NSFontAttributeName: [NSFont fontWithName:fontName size:fontSize] }];
CGFloat ratio = rect.size.width / sampleSize.width;
fontSize *= ratio;

确保样本的fontSize 足够大以获得良好的结果。

【讨论】:

  • 这是一个很好的估计。我必须在绘制文本的矩形中添加少量以避免截断。 Apple 必须使用不同的方法,因此需要一点填充来处理边缘条件。总而言之,我认为这是最好的解决方案。
  • 这里你只考虑宽度,框架的高度呢
  • 这假设高度是灵活的。您当然也可以计算高度的比率,然后取较低的比率(宽度与高度)并从中得出您的边界框并将其用于 fontSize。
【解决方案4】:

目前接受的答案是关于算法的,但 iOS 提供了 NSString 对象的计算。 我会使用NSString 类的sizeWithAttributes:

sizeWithAttributes:

返回接收器在使用给定属性绘制时占用的边界框大小。

    - (CGSize)sizeWithAttributes:(NSDictionary *)attributes

来源:Apple Docs - NSString UIKit Additions Reference

编辑误解了这个问题,所以这个答案是错误的。

【讨论】:

  • 这应该是最好的答案。从 iOS 7.0 开始,这是找出字符串边界矩形的最佳方法。
【解决方案5】:

更简单/更快(但当然是近似的)方式是这样的:

class func calculateOptimalFontSize(textLength:CGFloat, boundingBox:CGRect) -> CGFloat
    {
        let area:CGFloat = boundingBox.width * boundingBox.height
        return sqrt(area / textLength)
    }

我们假设每个字符是 N x N 像素,所以我们只计算 N x N 进入边界框的次数。

【讨论】:

  • 这个答案值得更多的支持。我同意你,它有点近似,但根据用例,它快速且相当准确。
  • 它也适用于我。用法示例:ClassName.calculateOptimalFontSize(textLength: CGFloat(quote.count), boundingBox: CGRect(origin: CGPoint(x: 0,y: 0), size: CGSize(width: self.frame.size.width, height: 50)))
【解决方案6】:

你可以使用 sizeWithFont :

[myString sizeWithFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:24]   
constrainedToSize:CGSizeMake(293, 10000)] // put the size of your frame

但它在 iOS 7 中已被弃用,所以我建议在 UILabel 中使用字符串:

[string sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0f]}];

如果您使用的是矩形:

CGRect textRect = [text boundingRectWithSize:mySize
                                 options:NSStringDrawingUsesLineFragmentOrigin
                              attributes:@{NSFontAttributeName:FONT}
                                 context:nil];

CGSize size = textRect.size;

【讨论】:

  • 但我正在使用固定框架 - 抱歉,如果我遗漏了什么但不确定这有什么帮助?
  • 如何从 CGSize 大小中提取字体大小(浮点数)?
  • 好的,你的问题是:“我怎样才能知道特定框架的完美字体大小?”,所以我认为没有答案,你必须自己使用上面的代码和一个@Fogmeister 所说的算法。
【解决方案7】:

您可以根据Apple's documentationUILabel的属性adjustsFontSizeToFitWidth设置为YES

【讨论】:

  • 我没有使用 UILabel。
  • 我的意思是你可以使用 UILabel 来获取字体大小
  • 我试过这个没有运气 - 它总是返回 17 作为字体大小。 UILabel * dummyLabel = [[UILabel alloc] initWithFrame:frameText]; dummyLabel.text = standString; dummyLabel.adjustsFontSizeToFitWidth = YES; UIView * dummyView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)]; [dummyView addSubview:dummyLabel]; float textSize = dummyLabel.font.pointSize;
  • 试试float textSize = dummyLabel.font.xHeight; :)
  • 调整字体大小意味着它将缩小字体以适应最小字体大小或设置的比例。
【解决方案8】:

下面的代码可以做到这一点:在一定范围内计算最佳字体大小。此示例在 UITextView 子类的上下文中,因此它使用其边界作为“给定框架”:

func binarySearchOptimalFontSize(min: Int, max: Int) -> Int {
    let middleSize = (min + max) / 2

    if min > max {
        return middleSize
    }

    let middleFont = UIFont(name: font!.fontName, size: CGFloat(middleSize))!

    let attributes = [NSFontAttributeName : middleFont]
    let attributedString = NSAttributedString(string: text, attributes: attributes)

    let size = CGSize(width: bounds.width, height: .greatestFiniteMagnitude)
    let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
    let textSize = attributedString.boundingRect(with: size, options: options, context: nil)

    if textSize.size.equalTo(bounds.size) {
        return middleSize
    } else if (textSize.height > bounds.size.height || textSize.width > bounds.size.width) {
        return binarySearchOptimalFontSize(min: min, max: middleSize - 1)
    } else {
        return binarySearchOptimalFontSize(min: middleSize + 1, max: max)
    }
}

希望对你有帮助。

【讨论】:

  • 如果 textSize 实际上不等于边界,则它不起作用。
【解决方案9】:

我喜欢@holtwick 提供的方法,但发现它有时会高估适合的方法。我创建了一个似乎在我的测试中运行良好的调整。提示:不要忘记使用“WWW”甚至“௵௵௵”等非常宽的字母进行测试

func idealFontSize(for text: String, font: UIFont, width: CGFloat) -> CGFloat {
    let baseFontSize = CGFloat(256)
    let textSize = text.size(attributes: [NSFontAttributeName: font.withSize(baseFontSize)])
    let ratio = width / textSize.width

    let ballparkSize = baseFontSize * ratio
    let stoppingSize = ballparkSize / CGFloat(2) // We don't want to loop forever, if we've already come down to 50% of the ballpark size give up
    var idealSize = ballparkSize
    while (idealSize > stoppingSize && text.size(attributes: [NSFontAttributeName: font.withSize(idealSize)]).width > width) {
        // We subtract 0.5 because sometimes ballparkSize is an overestimate of a size that will fit
        idealSize -= 0.5
    }

    return idealSize
}

【讨论】:

    【解决方案10】:

    这是我在 swift 4 中的解决方案:

    private func adjustedFontSizeOf(label: UILabel) -> CGFloat {
        guard let textSize = label.text?.size(withAttributes: [.font: label.font]), textSize.width > label.bounds.width else {
            return label.font.pointSize
        }
    
        let scale = label.bounds.width / textSize.width
        let actualFontSize = scale * label.font.pointSize
    
        return actualFontSize
    }
    

    我希望它对某人有所帮助。

    【讨论】:

      【解决方案11】:

      这是使用其他答案中的逻辑使动态字体大小随框架宽度变化的代码。 while 循环可能很危险,所以请不要犹豫提交改进。

      float fontSize = 17.0f; //initial font size
      CGSize rect;
      while (1) {
         fontSize = fontSize+0.1;
         rect = [watermarkText sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:fontSize]}];
          if ((int)rect.width == (int)subtitle1Text.frame.size.width) {
              break;
          }
      }
      subtitle1Text.fontSize = fontSize;
      

      【讨论】:

      • 有人能解释一下为什么它得到了一个负分吗?这段代码有问题吗?
      • 我没有投票给你,但我很确定是谁干的只是复制粘贴了这个 sn-p 没有结果。原因很可能是停止条件,它需要完全相同的宽度才能打破循环。应该是>=(如果你正在瞄准)或<=(否则)
      【解决方案12】:

      这是一种使用UITextView 对象似乎适用于iOS 9 的方法。您可能需要为其他应用程序发布一些推文。

      /*!
       * Find the height of the smallest rectangle that will enclose a string using the given font.
       *
       * @param string            The string to check.
       * @param font              The drawing font.
       * @param width             The width of the drawing area.
       *
       * @return The height of the rectngle enclosing the text.
       */
      
      - (float) heightForText: (NSString *) string font: (UIFont *) font width: (float) width {
          NSDictionary *fontAttributes = [NSDictionary dictionaryWithObject: font
                                                                     forKey: NSFontAttributeName];
          CGRect rect = [string boundingRectWithSize: CGSizeMake(width, INT_MAX)
                                             options: NSStringDrawingUsesLineFragmentOrigin
                                          attributes: fontAttributes
                                             context: nil];
          return rect.size.height;
      }
      
      /*!
       * Find the largest font size that will allow a block of text to fit in a rectangle of the given size using the system
       * font.
       *
       * The code is tested and optimized for UITextView objects.
       *
       * The font size is determined to ±0.5. Change delta in the code to get more or less precise results.
       *
       * @param string            The string to check.
       * @param size              The size of the bounding rectangle.
       *
       * @return: The font size.
       */
      
      - (float) maximumSystemFontSize: (NSString *) string size: (CGSize) size {
          // Hack: For UITextView, the last line is clipped. Make sure it's not one we care about.
          if ([string characterAtIndex: string.length - 1] != '\n') {
              string = [string stringByAppendingString: @"\n"];
          }
          string = [string stringByAppendingString: @"M\n"];
      
          float maxFontSize = 16.0;
          float maxHeight = [self heightForText: string font: [UIFont systemFontOfSize: maxFontSize] width: size.width];
          while (maxHeight < size.height) {
              maxFontSize *= 2.0;
              maxHeight = [self heightForText: string font: [UIFont systemFontOfSize: maxFontSize] width: size.width];
          }
      
          float minFontSize = maxFontSize/2.0;
          float minHeight = [self heightForText: string font: [UIFont systemFontOfSize: minFontSize] width: size.width];
          while (minHeight > size.height) {
              maxFontSize = minFontSize;
              minFontSize /= 2.0;
              maxHeight = minHeight;
              minHeight = [self heightForText: string font: [UIFont systemFontOfSize: minFontSize] width: size.width];
          }
      
          const float delta = 0.5;
          while (maxFontSize - minFontSize > delta) {
              float middleFontSize = (minFontSize + maxFontSize)/2.0;
              float middleHeight = [self heightForText: string font: [UIFont systemFontOfSize: middleFontSize] width: size.width];
              if (middleHeight < size.height) {
                  minFontSize = middleFontSize;
                  minHeight = middleHeight;
              } else {
                  maxFontSize = middleFontSize;
                  maxHeight = middleHeight;
              }
          }
      
          return minFontSize;
      }
      

      【讨论】:

        【解决方案13】:

        Apple 没有提供任何方法来找出适合给定矩形中文本的字体大小。想法是根据 BinarySearch 找出完全适合给定大小的最佳字体大小。以下扩展尝试不同的字体大小以收敛到完美的字体大小值。

        import UIKit
        
        extension UITextView {
            @discardableResult func adjustFontToFit(_ rect: CGSize, minFontSize: CGFloat = 5, maxFontSize: CGFloat = 100, accuracy: CGFloat = 0.1) -> CGFloat {
                // To avoid text overflow
                let targetSize = CGSize(width: floor(rect.width), height: rect.height)
        
                var minFontSize = minFontSize
                var maxFontSize = maxFontSize
                var fittingSize = targetSize
        
                while maxFontSize - minFontSize > accuracy {
                    let midFontSize = (minFontSize + maxFontSize) / 2
                    font = font?.withSize(midFontSize)
                    fittingSize = sizeThatFits(targetSize)
        
                    if fittingSize.height <= rect.height {
                        minFontSize = midFontSize
                    } else {
                        maxFontSize = midFontSize
                    }
                }
        
                // It might be possible that while loop break with last assignment
                // to `maxFontSize`, which can overflow the available height
                // Using `minFontSize` will be failsafe 
                font = font?.withSize(minFontSize)
                return minFontSize
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-05-22
          • 1970-01-01
          • 1970-01-01
          • 2023-01-30
          • 1970-01-01
          • 2017-04-03
          • 1970-01-01
          相关资源
          最近更新 更多