如何调整标签的字体大小以适应矩形?

问题描述:

是的,有这个很酷的myLabel.adjustsFontSizeToFitWidth = YES;属性。但只要标签有两行或更多行,它就不会将文本重新调整为任何东西。所以它会被截断...如果它不适合rect。如何调整标签的字体大小以适应矩形?

有没有另一种方法呢?

如果你想确保标签在宽度和高度方面都符合矩形,你可以在标签上尝试不同的字体大小,看看是否适合。

该片段始于300 pt,并尝试通过减小字体大小来使标签适合目标矩形。

- (void) sizeLabel: (UILabel *) label toRect: (CGRect) labelRect { 

    // Set the frame of the label to the targeted rectangle 
    label.frame = labelRect; 

    // Try all font sizes from largest to smallest font size 
    int fontSize = 300; 
    int minFontSize = 5; 

    // Fit label width wize 
    CGSize constraintSize = CGSizeMake(label.frame.size.width, MAXFLOAT); 

    do { 
     // Set current font size 
     label.font = [UIFont fontWithName:label.font.fontName size:fontSize]; 

     // Find label size for current font size 
     CGRect textRect = [[label text] boundingRectWithSize:constraintSize 
                options:NSStringDrawingUsesLineFragmentOrigin 
                attributes:@{NSFontAttributeName: label.font} 
                context:nil]; 

     CGSize labelSize = textRect.size; 

     // Done, if created label is within target size 
     if(labelSize.height <= label.frame.size.height) 
      break; 

     // Decrease the font size and try again 
     fontSize -= 2; 

    } while (fontSize > minFontSize); 
} 
+0

寻找几个小时后的无限数量,这一直是只为我工作的解决方案。我只是想知道当视图上有50-100个标签时,while循环的性能如何。 – agarcian 2013-01-31 03:23:52

+1

我从来没有任何理由去优化它。我不用它来重复计算。唯一的地方,我有很多标签来选择最长的标签文字和尺寸。 – 2014-03-04 12:17:47

+0

@NielsCastle你的解决方案是否也采用了文字包装技术?我在agarcian的答案中写了一个案例。它是否也满足这种情况? – 2014-11-19 01:06:56

还设置myLabel.numberOfLines = 10或无论您想要的最大数量的行。

+4

或者干脆0线 – 2011-09-21 15:47:28

+2

^这家伙知道他在谈论 – GangstaGraham 2013-08-26 10:29:20

我发现尼尔斯的答案是这个问题的最佳答案。不过,我有一个UIView,可以有100个标签,我需要适合文本,所以这个过程效率很低,我可以感受到性能上的冲击。

这是他的代码修改为使用二进制搜索,而不是线性搜索。现在它工作非常有效。

- (NSInteger)binarySearchForFontSizeForLabel:(UILabel *)label withMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize withSize:(CGSize)size { 
    // If the sizes are incorrect, return 0, or error, or an assertion. 
    if (maxFontSize < minFontSize) { 
     return 0; 
    } 

    // Find the middle 
    NSInteger fontSize = (minFontSize + maxFontSize)/2; 
    // Create the font 
    UIFont *font = [UIFont fontWithName:label.font.fontName size:fontSize]; 
    // Create a constraint size with max height 
    CGSize constraintSize = CGSizeMake(size.width, MAXFLOAT); 
    // Find label size for current font size 
    CGRect rect = [label.text boundingRectWithSize:constraintSize 
              options:NSStringDrawingUsesLineFragmentOrigin 
             attributes:@{NSFontAttributeName : font} 
              context:nil]; 
    CGSize labelSize = rect.size; 

    // EDIT: The next block is modified from the original answer posted in SO to consider the width in the decision. This works much better for certain labels that are too thin and were giving bad results. 
    if (labelSize.height >= (size.height + 10) && labelSize.width >= (size.width + 10) && labelSize.height <= (size.height) && labelSize.width <= (size.width)) { 
     return fontSize; 
    } else if (labelSize.height > size.height || labelSize.width > size.width) { 
     return [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:fontSize - 1 withSize:size]; 
    } else { 
     return [self binarySearchForFontSizeForLabel:label withMinFontSize:fontSize + 1 withMaxFontSize:maxFontSize withSize:size]; 
    } 
} 

- (void)sizeBinaryLabel:(UILabel *)label toRect:(CGRect)labelRect { 

    // Set the frame of the label to the targeted rectangle 
    label.frame = labelRect; 

    // Try all font sizes from largest to smallest font 
    int maxFontSize = 300; 
    int minFontSize = 5; 

    NSInteger size = [self binarySearchForFontSizeForLabel:label withMinFontSize:minFontSize withMaxFontSize:maxFontSize withSize:label.frame.size]; 

    label.font = [UIFont fontWithName:label.font.fontName size:size]; 

} 

信用此亦https://gist.github.com/988219

+1

+1 - 我只是想写一份二进制搜索解决方案,然后向下滚动以找出已经发布的二进制解决方案!这绝对是大多数情况下更好的解决方案。真棒agarcian。 – Pavan 2013-02-14 21:32:53

+2

谢谢帕文。这是SO的美丽。协作将为我们节省几小时和几小时的开发时间:-) – agarcian 2013-02-15 19:27:41

+0

非常优雅的解决方案 – 2013-03-21 13:04:54

尼尔斯城代码工作的发现。

下面是使用不同实现的相同想法。
我的解决方案更精确,但也要占用更多的CPU资源。

将此函数添加到继承UILabel的类。

-(void)fitCurrentFrame{ 

    CGSize iHave = self.frame.size; 

    BOOL isContained = NO; 
    do{ 
     CGSize iWant = [self.text sizeWithFont:self.font]; 
     if(iWant.width > iHave.width || iWant.height > iHave.height){ 
      self.font = [UIFont fontWithName:self.font.fontName size:self.font.pointSize - 0.1]; 
      isContained = NO; 
     }else{ 
      isContained = YES; 
     } 

    }while (isContained == NO); 
} 

如果有人正在寻找的MonoTouch/Xamarin.iOS实现,像我一样......在这里你去:

private int BinarySearchForFontSizeForText(NSString text, int minFontSize, int maxFontSize, SizeF size) 
{ 
    if (maxFontSize < minFontSize) 
     return minFontSize; 

    int fontSize = (minFontSize + maxFontSize)/2; 
    UIFont font = UIFont.BoldSystemFontOfSize(fontSize); 

    var constraintSize = new SizeF(size.Width, float.MaxValue); 
    SizeF labelSize = text.StringSize(font, constraintSize, UILineBreakMode.WordWrap); 

    if (labelSize.Height >= size.Height + 10 && labelSize.Width >= size.Width + 10 && labelSize.Height <= size.Height && labelSize.Width <= size.Width) 
     return fontSize; 
    else if (labelSize.Height > size.Height || labelSize.Width > size.Width) 
     return BinarySearchForFontSizeForText(text, minFontSize, fontSize - 1, size); 
    else 
     return BinarySearchForFontSizeForText(text, fontSize + 1, maxFontSize, size); 
} 

private void SizeLabelToRect(UILabel label, RectangleF labelRect) 
{ 
    label.Frame = labelRect; 

    int maxFontSize = 300; 
    int minFontSize = 5; 
    int size = BinarySearchForFontSizeForText(new NSString(label.Text), minFontSize, maxFontSize, label.Frame.Size); 

    label.Font = UIFont.SystemFontOfSize(size); 
} 

它的agarcian的距离的Objective-C到C#代码的转换,稍作修改:由于返回结果始终为0(see the comment of borked)我正在返回计算的minFontSize,这会导致正确的字体大小。

+0

非常感谢您的“小修改”。 – 2014-04-30 12:52:09

我已根据@ agarcian的回答为UILabel创建了类别。但我计算fontSize取决于屏幕上绘制文本所需的平方。这种方法不需要循环,计算是通过一次迭代完成的。

这里.h文件:

// UILabel+Extended.h 
// Created by Firuz on 16/08/14. 
// Copyright (c) 2014. All rights reserved. 

#import <UIKit/UIKit.h> 

@interface UILabel (Extended) 

/** This method calculate the optimal font size for current number of lines in UILable. Mus be called after drawing UILabel view */ 
- (NSInteger)fontSizeWithMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize; 

@end 

这里.m文件:

// UILabel+Extended.m 
// Created by Firuz on 16/08/14. 
// Copyright (c) 2014. All rights reserved. 

#import "UILabel+Extended.h" 

@implementation UILabel (Extended) 

- (NSInteger)fontSizeWithMinFontSize:(NSInteger)minFontSize withMaxFontSize:(NSInteger)maxFontSize 
{ 
    if (maxFontSize < minFontSize) { 
     return 0; 
    } 

    UIFont *font = [UIFont fontWithName:self.font.fontName size:maxFontSize]; 

    CGFloat lineHeight = [font lineHeight]; 

    CGSize constraintSize = CGSizeMake(MAXFLOAT, lineHeight); 

    CGRect rect = [self.text boundingRectWithSize:constraintSize 
              options:NSStringDrawingUsesLineFragmentOrigin 
             attributes:@{NSFontAttributeName : font} 
              context:nil]; 

    CGFloat labelSqr = self.frame.size.width * self.frame.size.height; 
    CGFloat stringSqr = rect.size.width/self.frame.size.width * (lineHeight + font.pointSize) * self.frame.size.width; 

    CGFloat multiplyer = labelSqr/stringSqr; 

    if (multiplyer < 1) { 
     if (minFontSize < maxFontSize*multiplyer) { 
      return maxFontSize * multiplyer; 
     } else { 
      return minFontSize; 
     } 
    } 
    return maxFontSize; 
} 

@end 

所有二进制搜索都是不错的,但使用帧检查没有那么逻辑上停止递归。更好地检查字体大小,导致UIFont支持浮点大小,这种字体更适合。 另外使用标签段落样式来精确计算尺寸。

如果有人感兴趣,你可以看看娄代码:

static UIFont * ___suitableFontInRangePrivate(const CGSize labelSize, 
              NSParagraphStyle * paragraphStyle, 
              NSString * fontName, 
              NSString * text, 
              const CGFloat minSize, 
              const CGFloat maxSize) 
{ 
    // Font size in range, middle size between max & min. 
    const CGFloat currentSize = minSize + ((maxSize - minSize)/2); 

    // Font with middle size. 
    UIFont * currentFont = [UIFont fontWithName:fontName size:currentSize]; 

    // Calculate text height. 
    const CGFloat textHeight = [text boundingRectWithSize:CGSizeMake(labelSize.width, CGFLOAT_MAX) 
               options:NSStringDrawingUsesLineFragmentOrigin 
              attributes:@{ NSFontAttributeName : currentFont, NSParagraphStyleAttributeName : paragraphStyle } 
               context:nil].size.height; 
    CGFloat min, max; 
    if (textHeight > labelSize.height) 
    { 
     // Take left range part. 
     min = minSize; 
     max = currentSize; 
    } 
    else 
    { 
     // Take right range part. 
     min = currentSize; 
     max = maxSize; 
    } 

    // If font size in int range [0.0; 2.0] - got it, othervice continue search. 
    return ((max - min) <= 2.0) ? currentFont : ___suitableFontInRangePrivate(labelSize, paragraphStyle, fontName, text, min, max); 
} 

void UILabelAdjustsFontSizeToFrame(UILabel * label) 
{ 
    if (!label) return; 

    NSString * text = [label text]; 

    __block NSParagraphStyle * style = nil; 
    [[label attributedText] enumerateAttributesInRange:NSMakeRange(0, [text length]) 
              options:(NSAttributedStringEnumerationOptions)0 
              usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop){ 
               id paragraphStyle = [attrs objectForKey:@"NSParagraphStyle"]; 
               if (paragraphStyle) style = [paragraphStyle retain]; 
              }]; 

    if (!style) 
    { 
     NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; 
     if (!paragraphStyle) paragraphStyle = [[NSMutableParagraphStyle alloc] init]; 
     if (paragraphStyle) 
     { 
      [paragraphStyle setLineBreakMode:[label lineBreakMode]]; 
      [paragraphStyle setAlignment:[label textAlignment]]; 
     } 
     style = paragraphStyle; 
    } 

    UIFont * suitableFont = ___suitableFontInRangePrivate([label frame].size, style, [[label font] fontName], text, 0, 500); 
    [label setFont:suitableFont]; 
    [style release]; 
} 

下面是根据@NielsCastle答案斯威夫特版,采用二进制搜索

extension UILabel{ 

    func adjustFontSizeToFitRect(rect : CGRect){ 

     if text == nil{ 
      return 
     } 

     frame = rect 

     let maxFontSize: CGFloat = 100.0 
     let minFontSize: CGFloat = 5.0 

     var q = Int(maxFontSize) 
     var p = Int(minFontSize) 

     let constraintSize = CGSize(width: rect.width, height: CGFloat.max) 

     while(p <= q){ 
      let currentSize = (p + q)/2 
      font = font.fontWithSize(CGFloat(currentSize)) 
      let text = NSAttributedString(string: self.text!, attributes: [NSFontAttributeName:font]) 
      let textRect = text.boundingRectWithSize(constraintSize, options: .UsesLineFragmentOrigin, context: nil) 

      let labelSize = textRect.size 

      if labelSize.height < frame.height && labelSize.height >= frame.height-10 && labelSize.width < frame.width && labelSize.width >= frame.width-10 { 
       break 
      }else if labelSize.height > frame.height || labelSize.width > frame.width{ 
       q = currentSize - 1 
      }else{ 
       p = currentSize + 1 
      } 
     } 

    } 
} 

使用

label.adjustFontSizeToFitRect(rect) 
经常只是

label.adjustFontSizeToFitRect(rect.frame) 
+0

不错,谢谢:) - 我只会建议使用[guard statement](https://www.hackingwithswift.com/new-syntax-swift-2-guard)取代这个早期回报。 – Mikolaj 2016-05-30 17:19:57

+0

@nRewik没有工作... – 2016-08-01 01:14:34

+0

@nRewik工作很好。只有部分,这是越野车是当你使'UILabel'相当高的高度,然后字体大小计算乱七八糟,它超出了宽度。将尝试解决这个错误,并在这里发布。 – 2016-09-13 07:24:54

该解决方案(基于this answer)的作品,支持自动布局和执行二进制搜索找到最好的字体大小。

我发现唯一的警告是你不能指定行数(因为AFAIK你不能告诉boundingRectWithSize你想要多少行)。

AdjustableLabel.h

#import <UIKit/UIKit.h> 

@interface AdjustableLabel : UILabel 
/** 
    If set to YES, font size will be automatically adjusted to frame. 
    Note: numberOfLines can't be specified so it will be set to 0. 
*/ 
@property(nonatomic) BOOL adjustsFontSizeToFitFrame; 
@end 

AdjustableLabel.m

#import "AdjustableLabel.h" 

@interface AdjustableLabel() 
@property(nonatomic) BOOL fontSizeAdjusted; 
@end 

// The size found S satisfies: S fits in the frame and and S+DELTA doesn't. 
#define DELTA 0.5 

@implementation AdjustableLabel 

- (void)setAdjustsFontSizeToFitFrame:(BOOL)adjustsFontSizeToFitFrame 
{ 
    _adjustsFontSizeToFitFrame = adjustsFontSizeToFitFrame; 

    if (adjustsFontSizeToFitFrame) { 
     self.numberOfLines = 0; // because boundingRectWithSize works like this was 0 anyway 
    } 
} 

- (void)layoutSubviews 
{ 
    [super layoutSubviews]; 

    if (self.adjustsFontSizeToFitFrame && !self.fontSizeAdjusted) 
    { 
     self.fontSizeAdjusted = YES; // to avoid recursion, because adjustFontSizeToFrame will trigger this method again 

     [self adjustFontSizeToFrame]; 
    } 
} 

- (void) adjustFontSizeToFrame 
{ 
    UILabel* label = self; 

    if (label.text.length == 0) return; 

    // Necessary or single-char texts won't be correctly adjusted 
    BOOL checkWidth = label.text.length == 1; 

    CGSize labelSize = label.frame.size; 

    // Fit label width-wise 
    CGSize constraintSize = CGSizeMake(checkWidth ? MAXFLOAT : labelSize.width, MAXFLOAT); 

    // Try all font sizes from largest to smallest font size 
    CGFloat maxFontSize = 300; 
    CGFloat minFontSize = 5; 

    NSString* text = label.text; 
    UIFont* font = label.font; 

    while (true) 
    { 
     // Binary search between min and max 
     CGFloat fontSize = (maxFontSize + minFontSize)/2; 

     // Exit if approached minFontSize enough 
     if (fontSize - minFontSize < DELTA/2) { 
      font = [UIFont fontWithName:font.fontName size:minFontSize]; 
      break; // Exit because we reached the biggest font size that fits 
     } else { 
      font = [UIFont fontWithName:font.fontName size:fontSize]; 
     } 

     // Find label size for current font size 
     CGRect rect = [text boundingRectWithSize:constraintSize 
             options:NSStringDrawingUsesLineFragmentOrigin 
             attributes:@{NSFontAttributeName : font} 
             context:nil]; 

     // Now we discard a half 
     if(rect.size.height <= labelSize.height && (!checkWidth || rect.size.width <= labelSize.width)) { 
      minFontSize = fontSize; // the best size is in the bigger half 
     } else { 
      maxFontSize = fontSize; // the best size is in the smaller half 
     } 
    } 

    label.font = font; 
} 

@end 

使用

AdjustableLabel* label = [[AdjustableLabel alloc] init]; 
label.adjustsFontSizeToFitFrame = YES; 

// In case you change the font, the size you set doesn't matter 
label.font = [UIFont fontWithName:@"OpenSans-Light" size:20]; 
+0

这。作品。完美。 – Fattie 2016-05-17 03:02:53

+0

这非常优雅,而且效果很好。我有一个小小的改进,它允许标签文本是单个单词的情况。在这种情况下,你不希望它跨行分割,所以我补充说: '//如果只有一个单词,那么请降低高度,以便强制boundingRectWithSize适合它'//一个line.' ''//' 如果([label.text componentsSeparatedByString:@”“] .Count之间== 1){'' labelSize.height/= 2.0;'' }'刚过 声明labelSize。 – PKCLsoft 2017-11-02 11:33:43

这里有一个斯威夫特内线UILabel的外挂。它运行一个二进制搜索算法来调整标签的字体和界限,并进行测试,以针对iOS 9.

USAGE工作:字体大小调整以适合尺寸的100×100(1.0字体点内准确)。

<label>.fitFontForSize(CGSizeMake(100.0, 100.0)) 

复制/粘贴以下到您的文件

extension UILabel { 

    func fitFontForSize(constrainedSize : CGSize, var maxFontSize : CGFloat = 300.0, var minFontSize : CGFloat = 5.0, accuracy : CGFloat = 1.0) { 
     assert(maxFontSize > minFontSize) 
     while maxFontSize - minFontSize > accuracy { 
      let midFontSize : CGFloat = ((minFontSize + maxFontSize)/2) 
      font = font.fontWithSize(midFontSize) 
      sizeToFit() 
      let checkSize : CGSize = bounds.size 
      if checkSize.height < constrainedSize.height && checkSize.width < constrainedSize.width { 
       minFontSize = midFontSize 
      } else { 
       maxFontSize = midFontSize 
      } 
     } 
     font = font.fontWithSize(minFontSize) 
     sizeToFit() 
    } 

} 

注: 标签的宽度和高度的限制不应会为了这个工作组。一个简单的解决方法是将标签嵌入到具有设置大小限制的UIView中,然后调用上面定义的函数(如下所示)。

<label>.fitFontForSize(<superview>.bounds.size) 
+1

不错的扩展;)请记住函数声明中的'var'参数实际上已被弃用,并将在Swift 3中删除。 – rafalkitta 2016-05-24 14:50:41

所有这些,都是对原来的问题有趣的解决方案,但是所有的人都还缺少一个重要的事情:如果你仅仅依靠familyName得到下一个字体进行测试,你失去的权重信息和可能更高级的属性,像小帽子,人物风格等

更好的办法是,而不是通过周围的字体名称和做[UIFont fontWithName:someFontName size:someFontSize],传递UIFontDescriptor对象一起,然后做 [UIFont fontWithDescriptor:someFontDescriptor size:someFontSize]

因为我没有找到工作解决方案使用上述答案回答我所有的需求,我创建了我自己的组件提供以下功能:使用时FittableFontLabel

  • 调整字体以适应高度和宽度使用单线标签时标签的多线
  • 调整字体,以适应宽度,高度标签将调整本身
  • NSAttributedStrings支持以及基本的字符串
  • 自动改变一拉时调整大小BEL文/帧
  • ...

如果你们中的任何有趣的是,它的使用 的CocoaPods全迅速可用库:https://github.com/tbaranes/FittableFontLabel

斯威夫特3“二进制搜索解决方案”的基础上this与答案小的改进。样本是在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) 
    } 
} 

我希望能帮助别人。

+0

这不起作用。将'greatestFiniteMagnitude'交换为宽度,如'CGSize(width:bounds.width,height:.greatestFiniteMagnitude)' – zrubenst 2017-07-01 21:15:07

@ agarcian的回答很接近,但它并没有完全为我工作,因为有人在评论别人提到的,它总是返回0。

这里是我的尝试。

干杯!

/** 
* Returns the font size required in order to fit the specified text in the specified area. 
* NB! When drawing, be sure to pass in the same options that we pass to boundingRectWithSize:options:attributes:context: 
* Heavily modified form of: http://*.com/a/14662750/1027452 
*/ 
+(NSInteger)fontSizeForText:(NSString *)text withFont:(UIFont *)font inArea:(CGSize)areaSize minFontSize:(NSInteger)minFontSize maxFontSize:(NSInteger)maxFontSize 
{ 
// If the sizes are incorrect, return 0, or error, or an assertion. 
    if (maxFontSize < minFontSize) { 
     return 0; 
    } 

    // Find the middle 
    NSInteger fontSize = (minFontSize + maxFontSize)/2; 
    // Create the font 
    UIFont *f = [UIFont fontWithName:font.fontName size:fontSize]; 
    // Create a constraint size with max height 
    CGSize constraintSize = CGSizeMake(areaSize.width, MAXFLOAT); 
    // Find label size for current font size 
    CGRect rect = [text boundingRectWithSize:constraintSize 
              options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) 
             attributes:@{NSFontAttributeName : f} 
              context:nil]; 
    CGSize labelSize = rect.size; 

    if (labelSize.height <= areaSize.height && labelSize.width <= areaSize.width) 
    { 
     return fontSize; 
    } 
    else if (labelSize.height > areaSize.height || labelSize.width > areaSize.width) 
    { 
     return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize maxFontSize:maxFontSize -1];; 
    } 
    else 
    { 
     return [self fontSizeForText:text withFont:f inArea:areaSize minFontSize:minFontSize+1 maxFontSize:maxFontSize];; 
    } 
}