ios 带有 adjustsFontSizeToFitWidth 的多行 UILabel

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4382976/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 18:15:32  来源:igfitidea点击:

Multiline UILabel with adjustsFontSizeToFitWidth

iosiphonecocoa-touchuikituilabel

提问by Ortwin Gentz

I have a multiline UILabel whose font size I'd like to adjust depending on the text length. The whole text should fit into the label's frame without truncating it.

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

Unfortunately, according to the documentation the adjustsFontSizeToFitWidthproperty "is effective only when the numberOfLinesproperty is set to 1".

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

I tried to determine the adjusted font size using

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

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

and then decrementing the font size until it fits. Unfortunately, this method internally truncates the text to fit into the specified size and returns the size of the resulting truncated string.

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

回答by Ortwin Gentz

In this question, 0x90 provides a solution that - although a bit ugly - does what I want. Specifically, it deals correctly with the situation that a single word does not fit the width at the initial font size. I've slightly modified the code so that it works as a category on NSString:

这个问题中, 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;
}

To use it with a UILabel:

使用它UILabel

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

EDIT: Fixed the code to initialize newFontwith font. Fixes a crash under certain circumstances.

编辑:固定的代码来初始化newFontfont。修复某些情况下的崩溃。

回答by weienw

In some cases, changing "Line Breaks" from "Word Wrap" to "Truncate Tail" may be all you need, if you know how many lines you want (e.g. "2"): Credit: Becky Hansmeyer

在某些情况下,如果您知道想要多少行(例如“2”),则可能只需要将“换行符”从“自动换行”更改为“截尾”即可:信用:Becky Hansmeyer

回答by Everton Cunha

Thanks, with that and a little more from someone else I did this custom UILabel, that will respect the minimum font size and there's a bonus option to align the text to top.

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

h:

H:

@interface EPCLabel : UILabel {
    float originalPointSize;
    CGSize originalSize;
}

@property (nonatomic, readwrite) BOOL alignTextOnTop;
@end

m:

米:

#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

I hope it helps.

我希望它有帮助。

回答by Kosta Eleftheriou

For a fully working solution, see the bottom of my answer

有关完全有效的解决方案,请参阅我的答案底部

To manually measure the dimensions of the text/ attributedTextof your UILabelin order to find the appropriate font size using your own strategy, you have a few options:

要手动测量text/attributedTextUILabel尺寸以使用您自己的策略找到合适的字体大小,您有几个选择:

  1. Use NSString's size(withAttributes:)or NSAttributedString's size()function. These are only partially useful because they assume the text is one line.

  2. Use NSAttributedString's boundingRect()function, which takes a few drawing options, making sure you supply .usesLineFragmentOriginto support multiple lines:

    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. Use TextKit and your own 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. Use CoreText, a more powerful and low-level API for laying out text. This would probably be unnecessarily complicated for this task.

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

  2. UseNSAttributedStringboundingRect()函数,它需要一些绘图选项,确保您提供.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,用于布置文本。对于此任务,这可能会不必要地复杂化。

Regardless of what you choose to use to measure text, you will probably need to do two passes: The first pass is to account for long words that should not be broken up over multiple lines, where you will need to find the largest font size that fits the largest (~longest) word fully within the label's bounds. In the second pass, you can continue your search downwards from the result of the first pass to find an even smaller font size as required to fit the entire text this time.

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

When doing the largest word measurement (rather than the entire text), you don't want to restrict the width parameter that you supply to some of the above sizing functions, otherwise the system will have no choice but to break up the single word you gave it and return incorrect results for your purposes. You will need to replace the width argument of the above methods with CGFloat.greatestFiniteMagnitude:

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

  • the width part of the size argument of boundingRect().
  • the width part of the size argument of NSTextContainer().
  • 的大小参数的宽度部分boundingRect()
  • 的大小参数的宽度部分NSTextContainer()

You will also need to consider the actual algorithm that you use for your search, since measuring text is an expensive operation and a linear search might be too slow, depending on your needs. If you want to be more efficient you can apply a binary search instead.

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

For a robust working solution based on the above, see my open-source framework AccessibilityKit. A couple of examples of AKLabelin action:

有关基于上述内容的可靠工作解决方案,请参阅我的开源框架AccessibilityKit。几个AKLabel在行动中的例子:

Example1

示例 1

Example2

例2

Hope this helps!

希望这可以帮助!

回答by Alexander Mayatsky

There is an ObjC extension provided in comments, that calculate fontsize required to fit multiline text into UILabel. It was rewritten in Swift (since it is 2016):

注释中提供了一个 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;
    }
}

Link to full code: https://gist.github.com/amayatsky/e6125a2288cc2e4f1bbf

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

回答by Zappel

Swift 4.2:

斯威夫特 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
    }
}

回答by Daniele Cux

It seems to me that there is now a better answer to this question

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

I have discovered that the font size adjusts automatically on a multiline label when you set adjustsFontSizeToFitWidth true, numberOfLines 0, a minimumScaleFactor little enough to let the font shrink as needed, and the default lineBreakMode (byTruncatingTail)

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

PS: I cannot find anything about this change on the official documentation. I created a question to find out more informations on this topic here

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