具有adjustsFontSizeToFitWidth的多行UILabel

时间:2010-12-08 00:04:20

标签: ios iphone cocoa-touch uikit uilabel

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

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

我尝试使用

确定调整后的字体大小
-[NSString (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode]

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

8 个答案:

答案 0 :(得分:50)

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

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

答案 1 :(得分:6)

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

答案 2 :(得分:3)

谢谢,有了这个,还有其他人,我做了这个自定义的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

我希望它有所帮助。

答案 3 :(得分:2)

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

答案 4 :(得分:2)

有关完整的解决方案,请参见答案的底部

要手动测量text的{​​{1}} / attributedText的尺寸,以便使用自己的策略找到合适的字体大小,您可以选择以下几种方法:

  1. 使用UILabel的{​​{1}}或NSString的{​​{1}}函数。这些只是部分有用,因为它们假定文本为一行。

  2. 使用size(withAttributes:)的{​​{1}}函数,该函数需要一些绘图选项,请确保提供NSAttributedString以支持多行:

    size()
  3. 使用TextKit和您自己的NSLayoutManager:

    NSAttributedString
  4. 使用CoreText,这是一种功能更强大的底层API,用于布置文本。对于此任务,这可能不必要地复杂。

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

在进行最大的字词量度(而不是整个文本)时,您不想将提供的width参数限制为上述某些大小调整函数,否则系统别无选择,只能将您给它一个字,并为您的目的返回不正确的结果。您将需要用boundingRect()替换上述方法的width参数:

  • .usesLineFragmentOrigin的size参数的宽度部分。
  • 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) 的size参数的宽度部分。

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

有关基于上述内容的可靠解决方案,请参见我的开源框架AccessibilityKit。实际使用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) 的几个示例:

Example1

Example2

希望这会有所帮助!

答案 5 :(得分:1)

我想在答案上加上自己的看法:

我认为在其他一些答案上略有改善,因为:

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

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

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

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

此线程上的其他答案对我提出解决方案非常有帮助,谢谢

答案 6 :(得分:0)

Swift 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 :(得分:0)

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

我发现,当您将AdjustsFontSizeToFitWidth设置为true,numberOfLines为0,最小缩放因子(足以使字体根据需要缩小)以及默认的lineBreakMode(byTruncatingTail)设置时,字体大小会在多行标签上自动调整。

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

的更多信息