调整 UILabel 大小以适应自动换行

发布于 2024-09-14 21:00:40 字数 1421 浏览 3 评论 0原文

这是 iPhone 应用程序的一部分,但通常适用于用 objC 编写的 Cocoa。

我有一个 UILabel 保存各种数量的文本(从单个字符到几个句子)。文本应始终以适合 UILabel 中所有文本的尽可能大的字体显示。 最大行数设置为 4,换行模式设置为自动换行。

由于使用了多行,因此 adjustmentFontSizeToFitWidth 无法用于调整文本大小。

因此,我使用循环来确定每个字符串的最大可能字体大小,如下所示:

 //设置文本  
    self.textLabel.text = 文本;
    //使用的最大尺寸  
    NSInteger fsize = 200;文本标签.font = [UIFont
    fontWithName:@"Verdana-Bold"
    大小:fsize];

    //使用当前参数计算渲染字符串的大小
    浮动高度 = [文本大小WithFont:textLabel.font
        constrainedToSize:CGSizeMake(textLabel.bounds.size.width,99999) 
        lineBreakMode:UILineBreakModeWordWrap].height;

    //字体太大时减小5,如果没有高度则中断(空字符串)
    while (height > textLabel.bounds.size.height and height != 0) {   
        fsize -= 5;  
        textLabel.font = [UIFont fontWithName:@"Verdana-Bold" 大小:fsize];   
        高度 = [文本大小WithFont:textLabel.font 
            constrainedToSize:CGSizeMake(textLabel.bounds.size.width,99999) 
            lineBreakMode:UILineBreakModeWordWrap].height;
    };

这种方法在大多数情况下都很有效。 长单词除外。 让我们以字符串@“The experience foo”为例。举个例子。 “experience”这个词比其他词长得多,如果没有正确换行,它就会被分成两半,并且字符串会被分成 4 行。 我正在寻找一种进一步减小大小的方法,以便每个单词都适合一行。

示例:

-old-

字体大小:60

<前><代码> 专家 科学 富

应该是

-new-

字体大小:30

<前><代码> 经验 富

可能有一种简单的方法可以做到这一点,但我遇到了困难。

This is part of an iPhone application but should apply to Cocoa written in objC in general.

I have a UILabel holding various amounts of text (from single characters to several sentences). The text should always be displayed in the largest possible font that fits all the text within the UILabel.
The maximum number of lines is set to 4 and the line break mode is set to word wrap.

Since multiple lines are used, adjustsFontSizeToFitWidth won't work for resizing the text.

Thus I am using a loop to determine the largest possible font size for each string as such:

    //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;
    };

This approach works well for the most part.
The exception are long words.
Let's take the string @"The experience foo." as an example.
The word "experience", being much longer than the others will be split in half without being word-wrapped correctly and the string split across 4 lines.
I am looking for a way to reduce the size further so that each individual word fits in one line.

Example:

-old-

Font size: 60

The
Exper
ience
foo

should be

-new-

Font size: 30

The
Experience
foo

There probably is an easy way to do this but I'm hitting a wall.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

烟雨扶苏 2024-09-21 21:00:40

这是我发现的最优雅(但有点黑客)的方法:

  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;

    }
}

Here is the most elegant (yet somewhat hackish) way I found to make this work:

  1. Split the string into words
  2. Calculate the width of each word using the current font size
  3. Reduce the size of the string until each the word fits into one line

Resource consumption is low enough for this to work even in UITableViews full of strings edited this way.

Here is the new code:

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

    }
}
囍笑 2024-09-21 21:00:40

这是我在某个类别中的 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

Here's my version of 0x90's answer in a category:

@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
走野 2024-09-21 21:00:40

的类别中使用上面的代码

您可以在 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

You can use the code above in a Category for 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
一场信仰旅途 2024-09-21 21:00:40

这是一个很好的问题,您可能会认为使用尽可能大的字体大小而不分解单词现在将成为内置 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

希望如此有帮助!

It's a great question, and you would think that using the largest possible font size without breaking words up would be part of the built-in UIKit functionality or a related framework by now. Here's a good visual example of the question:

Font resizing animation

As described by others, the trick is to perform the size search for individual words, as well as the entire text as a whole. This is because when you specify a width to draw single words into, the sizing methods will break the words up since they have no other choice - you are asking them to draw an "unbreakable" string, with a specific font size, into a region that simply doesn't fit.

At the heart of my working solution, I use the following binary search function:

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

This alone is not enough though. You need to use it to first find the maximum size that will fit the longest word inside the bounds. Once you have that, continue searching for a smaller size until the entire text fits. This way no word is ever going to be broken up. There are some additional considerations that are a bit more involved, including finding what the longest word actually is (there's some gotchas!) and iOS font caching performance.

If you only care about showing the text on the screen in an easy way, I have developed a robust implementation in Swift, which I'm also using in a production app. It's a UIView subclass with efficient, automatic font scaling for any input text, including multiple lines. To use it, you'd simply do something like:

let view = AKTextView()
// Use a simple or fancy NSAttributedString
view.attributedText = .init(string: "Some text here")
// Add to the view hierarchy somewhere

That's it! You can find the complete source code here: https://github.com/FlickType/AccessibilityKit

Hope this helps!

那一片橙海, 2024-09-21 21:00:40

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

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()
}

UILabel extension in Swift 4 based on 0x90's answer:

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()
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文