自动换行调整UILabel大小

9
这是iPhone应用程序的一部分,但适用于使用objC编写的Cocoa通用语言。
我有一个UILabel包含各种文本(从单个字符到几个句子)。文本应始终以最大可能的字体显示,以适合UILabel中的所有文本。 最大行数设置为4行,换行模式设置为自动换行。
由于使用了多行,调整adjustsFontSizeToFitWidth无法调整文本大小。
因此,我正在使用循环来确定每个字符串的最大可能字体大小,如下所示:
    //Set the text  
    self.textLabel.text = text;
    //Largest size used  
    NSInteger fsize = 200;  textLabel.font = [UIFont
    fontWithName:@"Verdana-Bold"
    size:fsize];

    //Calculate size of the rendered string with the current parameters
    float height = [text sizeWithFont:textLabel.font
        constrainedToSize:CGSizeMake(textLabel.bounds.size.width,99999) 
        lineBreakMode:UILineBreakModeWordWrap].height;

    //Reduce font size by 5 while too large, break if no height (empty string)
    while (height > textLabel.bounds.size.height and height != 0) {   
        fsize -= 5;  
        textLabel.font = [UIFont fontWithName:@"Verdana-Bold" size:fsize];   
        height = [text sizeWithFont:textLabel.font 
            constrainedToSize:CGSizeMake(textLabel.bounds.size.width,99999) 
            lineBreakMode:UILineBreakModeWordWrap].height;
    };

这种方法在大多数情况下都很有效。

例外是长单词。

让我们以字符串@"The experience foo."为例。

单词"experience"比其他单词长得多,将被错误地分成两半而没有正确的换行,导致字符串跨越4行。

我正在寻找一种进一步减小大小的方法,使每个单独的单词都适合一行。

例子:

-旧-

字体大小:60

The
Exper
ience
foo

应该是

-新-

字体大小:30

The
Experience
foo

可能有一种简单的方法来解决这个问题,但是我却遇到了阻碍。

5个回答

13

这是我发现的最优雅(但有点巧妙)的使其工作的方法:

  1. 将字符串拆分为单词
  2. 使用当前字体大小计算每个单词的宽度
  3. 减小字符串的大小,直到每个单词适合一行

资源消耗低到足以使其在充满以此方式编辑的字符串的UITableViews中工作。

以下是新代码:

//Set the text  
self.textLabel.text = text;
//Largest size used  
NSInteger fsize = 200;  textLabel.font = [UIFont fontWithName:@"Verdana-Bold"
                                                         size:fsize];

//Calculate size of the rendered string with the current parameters
float height = 
      [text sizeWithFont:textLabel.font
       constrainedToSize:CGSizeMake(textLabel.bounds.size.width,99999) 
           lineBreakMode:UILineBreakModeWordWrap].height;

//Reduce font size by 5 while too large, break if no height (empty string)
while (height > textLabel.bounds.size.height and height != 0) {   
    fsize -= 5;  
    textLabel.font = [UIFont fontWithName:@"Verdana-Bold" size:fsize];   
    height = [text sizeWithFont:textLabel.font 
              constrainedToSize:CGSizeMake(textLabel.bounds.size.width,99999) 
                  lineBreakMode:UILineBreakModeWordWrap].height;
};

// Loop through words in string and resize to fit
for (NSString *word in [text componentsSeparatedByString:@" "]) {
    float width = [word sizeWithFont:textLabel.font].width;
    while (width > textLabel.bounds.size.width and width != 0) {
        fsize -= 3;
        textLabel.font = [UIFont fontWithName:@"Verdana-Bold" size:fsize];
        width = [word sizeWithFont:textLabel.font].width;

    }
}

由于sizeWithFont:已经在iOS 7中被弃用,您应该将其替换为boundingRectWithSize: - AppsolutEinfach

4
下面是我对0x90的答案进行分类的版本:
@implementation UILabel (MultilineAutosize)

- (void)adjustFontSizeToFit
{
    //Largest size used
    NSInteger fsize = self.font.pointSize;

    //Calculate size of the rendered string with the current parameters
    float height = [self.text sizeWithFont:self.font
                         constrainedToSize:CGSizeMake(self.bounds.size.width, MAXFLOAT)
                             lineBreakMode:NSLineBreakByWordWrapping].height;

    //Reduce font size by 5 while too large, break if no height (empty string)
    while (height > self.bounds.size.height && height > 0) {
        fsize -= 5;
        self.font = [self.font fontWithSize:fsize];
        height = [self.text sizeWithFont:self.font
                       constrainedToSize:CGSizeMake(self.bounds.size.width, MAXFLOAT)
                           lineBreakMode:NSLineBreakByWordWrapping].height;
    };

    // Loop through words in string and resize to fit
    for (NSString *word in [self.text componentsSeparatedByString:@" "]) {
        float width = [word sizeWithFont:self.font].width;
        while (width > self.bounds.size.width && width > 0) {
            fsize -= 3;
            self.font = [self.font fontWithSize:fsize];
            width = [word sizeWithFont:self.font].width;
        }
    }
}

@end

3
您可以在UILabel的分类中使用上述代码。
UILabel+AdjustFontSize.h
@interface UILabel (UILabel_AdjustFontSize)

- (void) adjustsFontSizeToFitWidthWithMultipleLinesFromFontWithName:(NSString*)fontName size:(NSInteger)fsize andDescreasingFontBy:(NSInteger)dSize;

@end

UILabel+AdjustFontSize.m

@implementation UILabel (UILabel_AdjustFontSize)

- (void) adjustsFontSizeToFitWidthWithMultipleLinesFromFontWithName:(NSString*)fontName size:(NSInteger)fsize andDescreasingFontBy:(NSInteger)dSize{

    //Largest size used  
    self.font = [UIFont fontWithName:fontName size:fsize];

    //Calculate size of the rendered string with the current parameters
    float height = [self.text sizeWithFont:self.font
                    constrainedToSize:CGSizeMake(self.bounds.size.width,99999) 
                        lineBreakMode:UILineBreakModeWordWrap].height;

    //Reduce font size by dSize while too large, break if no height (empty string)
    while (height > self.bounds.size.height && height != 0) {   
        fsize -= dSize;
        self.font = [UIFont fontWithName:fontName size:fsize];   
        height = [self.text sizeWithFont:self.font 
                  constrainedToSize:CGSizeMake(self.bounds.size.width,99999) 
                      lineBreakMode:UILineBreakModeWordWrap].height;
    };

    // Loop through words in string and resize to fit
    for (NSString *word in [self.text componentsSeparatedByString:@" "]) {
        float width = [word sizeWithFont:self.font].width;
        while (width > self.bounds.size.width && width != 0) {
            fsize -= dSize;
            self.font = [UIFont fontWithName:fontName size:fsize];
            width = [word sizeWithFont:self.font].width;            
        }
    }
}

@end

1
尝试了你的代码,在 while 语句中进入了无限循环。 - Resh32

1

这是一个很好的问题,你可能认为在不打断单词的情况下使用最大可能的字体大小应该是内置的UIKit功能或相关框架的一部分。这里有一个很好的视觉示例:

Font resizing animation

正如其他人所描述的那样,诀窍在于对单个单词以及整个文本进行大小搜索。这是因为当您指定一个宽度来绘制单个单词时,大小调整方法将分解单词,因为它们别无选择-您要求它们将“不可打破”的字符串以特定字体大小绘制到一个根本不适合的区域中。
在我的解决方案中心,我使用以下二进制搜索函数:
func binarySearch(string: NSAttributedString, minFontSize: CGFloat, maxFontSize: CGFloat, maxSize: CGSize, options: NSStringDrawingOptions) -> CGFloat {
    let avgSize = roundedFontSize((minFontSize + maxFontSize) / 2)
    if avgSize == minFontSize || avgSize == maxFontSize { return minFontSize }
    let singleLine = !options.contains(.usesLineFragmentOrigin)
    let canvasSize = CGSize(width: singleLine ? .greatestFiniteMagnitude : maxSize.width, height: .greatestFiniteMagnitude)
    if maxSize.contains(string.withFontSize(avgSize).boundingRect(with: canvasSize, options: options, context: nil).size) {
      return binarySearch(string: string, minFontSize:avgSize, maxFontSize:maxFontSize, maxSize: maxSize, options: options)
    } else {
      return binarySearch(string: string, minFontSize:minFontSize, maxFontSize:avgSize, maxSize: maxSize, options: options)
    }
  }

这还不够。您需要先使用它找到适合边界内最长单词的最大尺寸。一旦找到,继续搜索更小的尺寸,直到整个文本适合为止。这样就不会打断任何单词。还有一些额外的考虑因素,包括找到实际上最长的单词(有一些陷阱!)和iOS字体缓存性能。
如果您只关心以简单的方式在屏幕上显示文本,我已经在Swift中开发了一个强大的实现,我也在生产应用程序中使用它。它是一个UIView子类,具有高效的自动字体缩放功能,适用于任何输入文本,包括多行。要使用它,您只需执行以下操作:
let view = AKTextView()
// Use a simple or fancy NSAttributedString
view.attributedText = .init(string: "Some text here")
// Add to the view hierarchy somewhere

就是这样!你可以在这里找到完整的源代码:https://github.com/FlickType/AccessibilityKit

希望这可以帮到你!


0

基于0x90的答案,Swift 4中的UILabel扩展:

func adjustFontSizeToFit() {
    guard var font = self.font, let text = self.text else { return }
    let size = self.frame.size
    var maxSize = font.pointSize
    while maxSize >= self.minimumScaleFactor * self.font.pointSize {
        font = font.withSize(maxSize)
        let constraintSize = CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude)
        let textRect = (text as NSString).boundingRect(with: constraintSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font : font], context: nil)
        let labelSize = textRect.size
        if labelSize.height <= size.height {
            self.font = font
            self.setNeedsLayout()
            break
        }
        maxSize -= 1
    }
    self.font = font;
    self.setNeedsLayout()
}

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