【问题标题】:Multiline UILabel with adjustsFontSizeToFitWidth带有adjustsFontSizeToFitWidth的多行UILabel
【发布时间】:2010-12-08 00:04:20
【问题描述】:

我有一个多行 UILabel,我想根据文本长度调整其字体大小。整个文本应该适合标签的框架而不截断它。

不幸的是,根据文档,adjustsFontSizeToFitWidth 属性“仅在 numberOfLines 属性设置为 1 时有效”。

我尝试使用来确定调整后的字体大小

-[NSString (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode]

然后减小字体大小直到合适为止。不幸的是,此方法在内部截断文本以适应指定的大小并返回生成的截断字符串的大小。

【问题讨论】:

    标签: ios iphone cocoa-touch uikit uilabel


    【解决方案1】:

    this question 中,0x90 提供了一个解决方案——虽然有点难看——可以满足我的需求。具体来说,它正确地处理了单个单词不适合初始字体大小的宽度的情况。我稍微修改了代码,使其作为NSString 上的一个类别:

    - (CGFloat)fontSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
        CGFloat fontSize = [font pointSize];
        CGFloat height = [self sizeWithFont:font constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
        UIFont *newFont = font;
    
        //Reduce font size while too large, break if no height (empty string)
        while (height > size.height && height != 0) {   
            fontSize--;  
            newFont = [UIFont fontWithName:font.fontName size:fontSize];   
            height = [self sizeWithFont:newFont constrainedToSize:CGSizeMake(size.width,FLT_MAX) lineBreakMode:UILineBreakModeWordWrap].height;
        };
    
        // Loop through words in string and resize to fit
        for (NSString *word in [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
            CGFloat width = [word sizeWithFont:newFont].width;
            while (width > size.width && width != 0) {
                fontSize--;
                newFont = [UIFont fontWithName:font.fontName size:fontSize];   
                width = [word sizeWithFont:newFont].width;
            }
        }
        return fontSize;
    }
    

    将其与UILabel 一起使用:

        CGFloat fontSize = [label.text fontSizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:label.frame.size];
        label.font = [UIFont boldSystemFontOfSize:fontSize];
    

    编辑:修复了使用font 初始化newFont 的代码。修复了某些情况下的崩溃问题。

    【讨论】:

    • 我发现使用 [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] 而不是仅仅按空格分割更准确。
    • 这很棒。为方便起见,我在UILabel 添加了对最小字体大小和类别的支持,并为感兴趣的人添加了uploaded it as a gist
    • 好方法。您可以考虑添加换行模式作为另一个参数。
    • @ObjectiveFlash,当然可能,虽然不是最好的风格。通常你会把它放在一个额外的 NSString+Additions.h/.m 文件中。然后你可以在任何你需要的地方导入它。
    • 我尝试将其转换为对 iOS 7 友好,但我无法获得相同的结果。 iOS 7 有什么类似的方法吗?
    【解决方案2】:

    在某些情况下,如果您知道想要多少行(例如“2”),将“换行符”从“Word Wrap”更改为“Truncate Tail”可能就足够了:Credit:Becky Hansmeyer

    【讨论】:

      【解决方案3】:

      要获得完全有效的解决方案,请参阅我的答案底部?

      要手动测量 UILabeltext / attributedText 的尺寸,以便使用您自己的策略找到合适的字体大小,您有以下几种选择:

      1. 使用NSStringsize(withAttributes:)NSAttributedStringsize() 函数。这些只是部分有用,因为它们假设文本是一行。

      2. 使用NSAttributedStringboundingRect() 函数,它需要一些绘图选项,确保提供.usesLineFragmentOrigin 以支持多行:

        var textToMeasure = label.attributedText
        
        // Modify the font size in `textToMeasure` as necessary
        
        // Now measure
        let rect = textToMeasure.boundingRect(with: label.bounds, options: [. usesLineFragmentOrigin], context: nil)
        
      3. 使用 TextKit 和你自己的 NSLayoutManager:

        var textToMeasure = label.attributedText
        
        // Modify the font size in `textToMeasure` as necessary
        
        // Now measure
        let layoutManager = NSLayoutManager()
        let textContainer = NSTextContainer(size: CGSize(width: label.bounds.width, height: .greatestFiniteMagnitude))
        let textStorage = NSTextStorage(attributedString: string)
        textStorage.addLayoutManager(layoutManager)
        layoutManager.addTextContainer(textContainer)
        let glyphRange = layoutManager.glyphRange(for: textContainer)
        let rect = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
        
      4. 使用 CoreText,这是一种用于布局文本的功能更强大的低级 API。对于这项任务,这可能会变得不必要地复杂。

      无论您选择使用什么来测量文本,您都可能需要执行两遍:第一遍是考虑不应分成多行的长词,您需要在其中找到最大的完全适合标签范围内的最大(~最长)单词的字体大小。在第二遍中,您可以从第一遍的结果继续向下搜索,以找到适合本次整个文本所需的更小的字体大小。

      在进行最大字测量(而不是整个文本)时,您不希望将您提供的宽度参数限制为上述某些尺寸功能,否则系统将别无选择,只能分解您给它的单个单词并为您的目的返回不正确的结果。您需要将上述方法的宽度参数替换为CGFloat.greatestFiniteMagnitude

      • boundingRect() 的 size 参数的宽度部分。
      • NSTextContainer() 的 size 参数的宽度部分。

      您还需要考虑用于搜索的实际算法,因为测量文本是一项昂贵的操作,而线性搜索可能会太慢,具体取决于您的需要。如果您想提高效率,可以改为应用二进制搜索。 ?

      有关基于上述内容的强大工作解决方案,请参阅我的开源框架AccessibilityKitAKLabel 的几个例子:

      希望这会有所帮助!

      【讨论】:

        【解决方案4】:

        谢谢,我做了这个自定义 UILabel 的其他人的更多信息,这将尊重最小字体大小,并且有一个额外的选项可以将文本对齐到顶部。

        h:

        @interface EPCLabel : UILabel {
            float originalPointSize;
            CGSize originalSize;
        }
        
        @property (nonatomic, readwrite) BOOL alignTextOnTop;
        @end
        

        米:

        #import "EPCLabel.h"
        
        @implementation EPCLabel
        @synthesize alignTextOnTop;
        
        -(void)verticalAlignTop {
            CGSize maximumSize = originalSize;
            NSString *dateString = self.text;
            UIFont *dateFont = self.font;
            CGSize dateStringSize = [dateString sizeWithFont:dateFont 
                                           constrainedToSize:CGSizeMake(self.frame.size.width, maximumSize.height)
                                               lineBreakMode:self.lineBreakMode];
        
            CGRect dateFrame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, dateStringSize.height);
        
            self.frame = dateFrame;
        }
        
        - (CGFloat)fontSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size {
            CGFloat fontSize = [font pointSize];
            CGFloat height = [self.text sizeWithFont:font             
                                   constrainedToSize:CGSizeMake(size.width,FLT_MAX)  
                                       lineBreakMode:UILineBreakModeWordWrap].height;
            UIFont *newFont = font;
        
            //Reduce font size while too large, break if no height (empty string)
            while (height > size.height && height != 0 && fontSize > self.minimumFontSize) { 
                fontSize--;  
                newFont = [UIFont fontWithName:font.fontName size:fontSize];   
                height = [self.text sizeWithFont:newFont  
                               constrainedToSize:CGSizeMake(size.width,FLT_MAX) 
                                   lineBreakMode:UILineBreakModeWordWrap].height;
            };
        
            // Loop through words in string and resize to fit
            if (fontSize > self.minimumFontSize) {
                for (NSString *word in [self.text componentsSeparatedByString:@" "]) {
                    CGFloat width = [word sizeWithFont:newFont].width;
                    while (width > size.width && width != 0 && fontSize > self.minimumFontSize) {
                        fontSize--;
                        newFont = [UIFont fontWithName:font.fontName size:fontSize];   
                        width = [word sizeWithFont:newFont].width;
                    }
                }
            }
            return fontSize;
        }
        
        -(void)setText:(NSString *)text {
            [super setText:text];
            if (originalSize.height == 0) {
                originalPointSize = self.font.pointSize;
                originalSize = self.frame.size;
            }
        
            if (self.adjustsFontSizeToFitWidth && self.numberOfLines > 1) {
                UIFont *origFont = [UIFont fontWithName:self.font.fontName size:originalPointSize];
                self.font = [UIFont fontWithName:origFont.fontName size:[self fontSizeWithFont:origFont constrainedToSize:originalSize]];
            }
        
            if (self.alignTextOnTop) [self verticalAlignTop];
        }
        
        -(void)setAlignTextOnTop:(BOOL)flag {
            alignTextOnTop = YES;
            if (alignTextOnTop && self.text != nil)
                [self verticalAlignTop];
        }
        
        @end
        

        希望对你有帮助。

        【讨论】:

        • 但是,使用所有这些方法,如果您的文本在起始(现在是最大)字体大小处不适合指定的行数,则 constrainedToSize 调用会根据您的行愉快地为您截断文本打破,以适应宽度。因此,您最终只会缩小以适应剩下的内容,而不是我认为您真正想要的内容,即“缩小以使所有这些文本适合 n 行”
        【解决方案5】:

        cmets 中提供了一个 ObjC 扩展,用于计算将多行文本放入 UILabel 所需的字体大小。 它是用 Swift 重写的(从 2016 年开始):

        //
        //  NSString+KBAdditions.swift
        //
        //  Created by Alexander Mayatsky on 16/03/16.
        //
        //  Original code from http://stackoverflow.com/a/4383281/463892 & http://stackoverflow.com/a/18951386
        //
        
        import Foundation
        import UIKit
        
        protocol NSStringKBAdditions {
            func fontSizeWithFont(font: UIFont, constrainedToSize size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat
        }
        
        extension NSString : NSStringKBAdditions {
            func fontSizeWithFont(font: UIFont, constrainedToSize size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat {
                var fontSize = font.pointSize
                let minimumFontSize = fontSize * minimumScaleFactor
        
        
                var attributedText = NSAttributedString(string: self as String, attributes:[NSFontAttributeName: font])
                var height = attributedText.boundingRectWithSize(CGSize(width: size.width, height: CGFloat.max), options:NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil).size.height
        
                var newFont = font
                //Reduce font size while too large, break if no height (empty string)
                while (height > size.height && height != 0 && fontSize > minimumFontSize) {
                    fontSize--;
                    newFont = UIFont(name: font.fontName, size: fontSize)!
        
                    attributedText = NSAttributedString(string: self as String, attributes:[NSFontAttributeName: newFont])
                    height = attributedText.boundingRectWithSize(CGSize(width: size.width, height: CGFloat.max), options:NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil).size.height
                }
        
                // Loop through words in string and resize to fit
                for word in self.componentsSeparatedByCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) {
                    var width = word.sizeWithAttributes([NSFontAttributeName:newFont]).width
                    while (width > size.width && width != 0 && fontSize > minimumFontSize) {
                        fontSize--
                        newFont = UIFont(name: font.fontName, size: fontSize)!
                        width = word.sizeWithAttributes([NSFontAttributeName:newFont]).width
                    }
                }
                return fontSize;
            }
        }
        

        完整代码链接:https://gist.github.com/amayatsky/e6125a2288cc2e4f1bbf

        【讨论】:

          【解决方案6】:

          斯威夫特 4.2:

          //
          //  String+Utility.swift
          //
          //  Created by Philip Engberg on 29/11/2018.
          //  Original code from http://stackoverflow.com/a/4383281/463892 & http://stackoverflow.com/a/18951386
          //
          
          import Foundation
          import UIKit
          
          extension String {
              func fontSize(with font: UIFont, constrainedTo size: CGSize, minimumScaleFactor: CGFloat) -> CGFloat {
                  var fontSize = font.pointSize
                  let minimumFontSize = fontSize * minimumScaleFactor
          
                  var attributedText = NSAttributedString(string: self, attributes: [.font: font])
                  var height = attributedText.boundingRect(with: CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], context: nil).size.height
          
                  var newFont = font
                  //Reduce font size while too large, break if no height (empty string)
                  while height > size.height && height != 0 && fontSize > minimumFontSize {
                      fontSize -= 1
                      newFont = UIFont(name: font.fontName, size: fontSize)!
          
                      attributedText = NSAttributedString(string: self, attributes: [.font: newFont])
                      height = attributedText.boundingRect(with: CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], context: nil).size.height
                  }
          
                  // Loop through words in string and resize to fit
                  for word in self.components(separatedBy: NSCharacterSet.whitespacesAndNewlines) {
                      var width = word.size(withAttributes: [.font: newFont]).width
                      while width > size.width && width != 0 && fontSize > minimumFontSize {
                          fontSize -= 1
                          newFont = UIFont(name: font.fontName, size: fontSize)!
                          width = word.size(withAttributes: [.font: newFont]).width
                      }
                  }
                  return fontSize
              }
          }
          

          【讨论】:

            【解决方案7】:

            在我看来,这个问题现在有了更好的答案

            我发现当您设置 adjustsFontSizeToFitWidth true、numberOfLines 0、minimumScaleFactor 小到足以让字体根据需要缩小以及默认 lineBreakMode (byTruncatingTail) 时,字体大小会在多行标签上自动调整

            PS:我在官方文档中找不到有关此更改的任何信息。我创建了一个问题以了解有关此主题的更多信息here

            【讨论】:

              【解决方案8】:

              我想我会添加我自己的答案:

              我认为这比其他一些答案略有改进,因为:

              1. 在遍历单词时进行缓存以按宽度查找最长的单词,以防止不必要的计算
              2. 在进行尺寸计算时使用所有标签属性

              https://gist.github.com/chrisjrex/c571056a4b621f7099bcbd5e179f184f

              注意:仅当 UILabel 具有 .numberOfLines = 0.lineBreakMode = .byWordWrapping 时才应使用此解决方案

              否则你最好使用标准 swift 库中的.adjustFontSizeForWidth = true

              这个帖子上的其他答案对我提出解决方案非常有帮助,谢谢

              【讨论】:

                猜你喜欢
                • 2012-10-06
                • 1970-01-01
                • 1970-01-01
                • 2017-01-08
                • 2012-08-03
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-10-02
                相关资源
                最近更新 更多