如何将字体大小设置为填充UILabel的高度?

66

我看过很多关于改变UILabel大小的例子。

这是我想做的事情: 更改字体大小,使文本在新高度内尽可能大。

有什么提示吗?


1
我刚刚意识到执行二进制或其他搜索是完全不必要的。你只需要使用比率搜索迭代(几次)。这很容易。我将把完整的代码粘贴作为答案。 - Fattie
1
2016年的完整解决方案 https://dev59.com/Qmoy5IYBdhLWcg3wCpsz#37277874。代码非常简单明了。 - Fattie
21个回答

42

我之前也遇到了同样的问题,通过这个帖子和Joel的算法,我解决了它。 :-)

以下是我用Swift编写的代码。我在iOS 8 + Autolayout中使用。

问题:

  1. 用户输入开支:

123 app

  1. 当用户点击“检查”按钮时,菜单从底部出现,将所有内容推向屏幕顶部(收缩所有内容,包括标签):

123 app

修复后:

123 app

这正是设计师想要的... :)

xScope app :)

我创建了UILabel的子类,并覆盖了layoutSubviews方法。每次UILabel尺寸改变时,都会重新计算字体大小:

//
//  LabelWithAdaptiveTextHeight.swift
//  123
//
//  Created by https://github.com/backslash-f on 12/19/14.
//

/*
 Designed with single-line UILabels in mind, this subclass 'resizes' the label's text (it changes the label's font size)
 everytime its size (frame) is changed. This 'fits' the text to the new height, avoiding undesired text cropping.
 Kudos to this Stack Overflow thread: bit.ly/setFontSizeToFillUILabelHeight
*/

import Foundation
import UIKit

class LabelWithAdaptiveTextHeight: UILabel {

    override func layoutSubviews() {
        super.layoutSubviews()
        font = fontToFitHeight()
    }

    // Returns an UIFont that fits the new label's height.
    private func fontToFitHeight() -> UIFont {

        var minFontSize: CGFloat = DISPLAY_FONT_MINIMUM // CGFloat 18
        var maxFontSize: CGFloat = DISPLAY_FONT_BIG     // CGFloat 67
        var fontSizeAverage: CGFloat = 0
        var textAndLabelHeightDiff: CGFloat = 0

        while (minFontSize <= maxFontSize) {

            fontSizeAverage = minFontSize + (maxFontSize - minFontSize) / 2

            // Abort if text happens to be nil
            guard text?.characters.count > 0 else {
              break
            }

            if let labelText: NSString = text {
                let labelHeight = frame.size.height

                let testStringHeight = labelText.sizeWithAttributes(
                    [NSFontAttributeName: font.fontWithSize(fontSizeAverage)]
                ).height

                textAndLabelHeightDiff = labelHeight - testStringHeight

                if (fontSizeAverage == minFontSize || fontSizeAverage == maxFontSize) {
                    if (textAndLabelHeightDiff < 0) {
                        return font.fontWithSize(fontSizeAverage - 1)
                    }
                    return font.fontWithSize(fontSizeAverage)
                }

                if (textAndLabelHeightDiff < 0) {
                    maxFontSize = fontSizeAverage - 1

                } else if (textAndLabelHeightDiff > 0) {
                    minFontSize = fontSizeAverage + 1

                } else {
                    return font.fontWithSize(fontSizeAverage)
                }
            }
        }
        return font.fontWithSize(fontSizeAverage)
    }
}


1
当然。我还没有遇到这段代码的任何性能问题。目前为止,一切都很好。再次感谢。 - backslash-f
2
这很棒,但如果您的标签文本恰好为nil,则会陷入while循环中。我尝试编辑帖子,但被拒绝了,所以我将其留给作者。 - Nightly
1
@Nightly 很好的发现!我更新了答案,包括你的建议并进行了一些小修改。测试过了,运行良好。谢谢! - backslash-f
1
@RobOsborne 很棒的发现,那是Xcode的默认类模板……我已经将其删除。谢谢。 - backslash-f
2
这让我很开心。我不明白为什么苹果一开始就没有做这个。 - Tob
显示剩余8条评论

38

有一个更简单的解决方案。只需添加以下几行代码,标签的字体大小就会神奇地调整以适合标签的高度:

SWIFT 3:

label.minimumScaleFactor = 0.1    //or whatever suits your need
label.adjustsFontSizeToFitWidth = true    
label.lineBreakMode = .byClipping
label.numberOfLines = 0

1
关键在于“byClipping”。我在界面构建器中设置了“WordWrap”,导致所有设置都没有任何效果。 - GameSalutes

36

以下是我的做法,因为 DGund 的回答对我不起作用,它适应了宽度,但我想让它适应高度。

+ (UIFont *)findAdaptiveFontWithName:(NSString *)fontName forUILabelSize:(CGSize)labelSize withMinimumSize:(NSInteger)minSize
{
    UIFont *tempFont = nil;
    NSString *testString = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    NSInteger tempMin = minSize;
    NSInteger tempMax = 256;
    NSInteger mid = 0;
    NSInteger difference = 0;

    while (tempMin <= tempMax) {
        mid = tempMin + (tempMax - tempMin) / 2;
        tempFont = [UIFont fontWithName:fontName size:mid];
        difference = labelSize.height - [testString sizeWithFont:tempFont].height;

        if (mid == tempMin || mid == tempMax) {
            if (difference < 0) {
                return [UIFont fontWithName:fontName size:(mid - 1)];
            }

            return [UIFont fontWithName:fontName size:mid];
        }

        if (difference < 0) {
            tempMax = mid - 1;
        } else if (difference > 0) {
            tempMin = mid + 1;
        } else {
            return [UIFont fontWithName:fontName size:mid];
        }
    }

    return [UIFont fontWithName:fontName size:mid];
}

这将需要一个字体名称,一个大小(理论上它不一定是UILabel,但我总是与UILabel一起使用),以及一个最小大小(您也可以使用最大大小,只需将256替换为最大尺寸参数)。它将在最小和最大字体大小之间测试每个字体大小,并返回高度等于或略小于目标高度的字体大小。

用法很简单,看起来像这样:

self.myLabel.font = [self findAdaptiveFontWithName:@"HelveticaNeue-UltraLight" forUILabelSize:self.myLabel.frame.size withMinimumSize:30];

你还可以将这个方法作为 UIFont 的类方法类别(这正是我所做的)。

编辑:经建议,我移除了 for 循环并花了一点时间用二分查找算法使其更加高效。我进行了几项检查以确保字体最终适合标签内。初步测试表明它有效。


4
你可能希望通过使用二分搜索算法来加快此算法的速度。 - Rob van der Veer
@RobvanderVeer 我看到的问题是我不知道我正在寻找的确切“解决方案”。它可能恰好是高度的大小,或者它可能更小,确实它将恰好比标签高度更大的那个小一个,但我还没有想过如何将其转换为更有效的算法。因此,虽然它们在技术上已排序,但在测试之前我没有这些值。二分查找会让我比手动循环更接近答案。 - Joel Fischer
1
@RobvanderVeer 添加了二分查找。如果您发现任何问题,请告诉我。 - Joel Fischer
1
你可能需要将tempFont从UIFont更改为NSDictionary,然后为其赋值;@{NSFontAttributeName: [UIFont fontWithName:fontName size:mid]};我认为自iOS7起,fontWithName已被弃用。 - James Wolfe
这个算法显著提高了我的项目性能。非常感谢! - ilovecomputer

24

编辑:查看Joel Fischer的绝佳答案,以编程方式获取正确的大小!

您可以将字体设置为自动填充标签大小,并选择不低于最小字体大小。只需将adjustsFontSizeToFitWidth设置为YES即可。如果需要更多信息,请查看UILabel类参考文档

虽然布尔值称为“adjustsFontSizeToFitWidth”,但它实际上意味着标签高度上保持单行(或者您指定的任意行数)的最大字体大小。


3
设置一个非常大的字体,使用这个属性比编写代码要容易得多。 - David Dunham
1
@DevinGund 或许我应该重新表达一下我的初始问题,因为这并不能满足我的需求。我想能够为标签设置特定的高度。然后添加任意数量的文本并更改字体大小,以适应高度。最后,我需要扩大标签以便可以看到所有的文本(1 行)。这样清楚明了吗?谢谢。 - wayneh
4
如果没有任何反应,那么你的字体大小不够大(或者已经达到边界范围内的最大值)。 - David Dunham
87
对于我来说,它的宽度适合,但高度绝对不合适。 - Joel Fischer
37
为了让它正常工作,我还需要将 numberOfLines 设为 0。 - Mikrasya
显示剩余5条评论

15

我已经根据我的标签高度调整了文本,我已经将Joel的方法转换为Swift

func optimisedfindAdaptiveFontWithName(fontName:String, label:UILabel!, minSize:CGFloat,maxSize:CGFloat) -> UIFont!
{

    var tempFont:UIFont
    var tempHeight:CGFloat
    var tempMax:CGFloat = maxSize
    var tempMin:CGFloat = minSize

    while (ceil(tempMin) != ceil(tempMax)){
        let testedSize = (tempMax + tempMin) / 2


        tempFont = UIFont(name:fontName, size:testedSize)
        let attributedString = NSAttributedString(string: label.text!, attributes: [NSFontAttributeName : tempFont])

        let textFrame = attributedString.boundingRectWithSize(CGSize(width: label.bounds.size.width, height: CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin , context: nil)

        let difference = label.frame.height - textFrame.height
        println("\(tempMin)-\(tempMax) - tested : \(testedSize) --> difference : \(difference)")
        if(difference > 0){
            tempMin = testedSize
        }else{
            tempMax = testedSize
        }
    }


    //returning the size -1 (to have enought space right and left)
    return UIFont(name: fontName, size: tempMin - 1)
}

而我是这样使用它的:

myLabel.font = optimisedfindAdaptiveFontWithName("Helvetica", label: myLabel, minSize: 10, maxSize: 38)
    println("\(myLabel.font)")

这个程序运行得非常好,除了在使用小写字母时。有时它们会超出标签的范围 :< - ClockWise
确实有效,但 var tempHeight:CGFloat 不是必需的。 - S1U
1
但是....我把它用作UIFont扩展,没有进行更改,代码如aLabel.font.optimisedfindAdaptiveFontWithName(".SFUIText-Regular", label: fontLabel, minSize: 10, maxSize: 100)。问题出在哪里? - S1U

10

好消息,

进行二分查找完全不必要!

您只需要使用比率搜索迭代(几次)即可。

        guess = guess * ( desiredHeight / guessHeight )

这里是完整的IBDesignable解决方案。
注意:与设计师或排版师合作时,您需要设置字体的跟踪/拉伸。 (令人荒谬的是,苹果没有包括这一点。)StyledLabel还包括跟踪/拉伸。
StyledLabel.swift
它设置了跟踪、拉伸,并将点大小设置为所有设备上的视图框架高度。
在Storyboard中:只需使UILabel的框架高度与您想要的文本高度相同 - 结束故事!
// the call fontToFitHeight FINDS THE POINT SIZE TO "FILL TO HEIGHT".
// Just use autolayout to make the frame THE ACTUAL HEIGHT
// you want the type ON ANY DEVICE

// ADDITIONALLY you can set:
// the tracking (that's the overall amount of space between all letters)
// and streching (actually squeeze or stretch the letters horizontally)

// Note: tracking and stretching IS SHOWN IN STORYBOARD LIVE
// WTT crazyrems http://stackoverflow.com/a/37300130/294884

import UIKit

@IBDesignable
class StyledLabel: UILabel
    {
    @IBInspectable var tracking:CGFloat = 0.8
    // values between about 0.7 to 1.3.  one means normal.
    
    @IBInspectable var stretching:CGFloat = -0.1
    // values between about -.5 to .5.  zero means normal.
    
    override func awakeFromNib()
        {
        tweak()
        }
    
    override func prepareForInterfaceBuilder()
        {
        tweak()
        }
    
    override func layoutSubviews()
        {
        super.layoutSubviews()
        font = fontToFitHeight()
        }
    
    private func fontToFitHeight() -> UIFont
        {
/* Apple have failed to include a basic thing needed in handling text: fitting the text to the height. Here's the simplest and fastest way to do that:

        guess = guess * ( desiredHeight / guessHeight )

That's really all there is to it. The rest of the code in this routine is safeguards. Further, the routine iterates a couple of times, which is harmless, to take care of any theoretical bizarre nonlinear sizing issues with strange typefaces. */
        
        guard text?.characters.count > 0 else { return font }
        let desiredHeight:CGFloat = frame.size.height
        guard desiredHeight>1 else { return font }
        var guess:CGFloat
        var guessHeight:CGFloat
        
        print("searching for... ", desiredHeight)
        
        guess = font.pointSize
        if (guess>1&&guess<1000) { guess = 50 }
        
        guessHeight = sizeIf(guess)
        
        if (guessHeight==desiredHeight)
            {
            print("fluke, exact match within float math limits, up front")
            return font.fontWithSize(guess)
            }
        
        var iterations:Int = 4
        
/* It is incredibly unlikely you would need more than four iterations, "two" would rarely be needed. You could imagine some very strange glyph handling where the relationship is non-linear (or something weird): That is the only theoretical reason you'd ever need more than one or two iterations. Note that when you watch the output of the iterations, you'll sometimes/often see same or identical values for the result: this is correct and expected in a float iteration. */
        
        while(iterations>0)
            {
            guess = guess * ( desiredHeight / guessHeight )
            guessHeight = sizeIf(guess)
            
            if (guessHeight==desiredHeight)
                {
                print("unbelievable fluke, exact match within float math limits while iterating")
                return font.fontWithSize(guess)
                }
            
            iterations -= 1
            }
        
        print("done. Shame Apple doesn't do this for us!")
        return font.fontWithSize(guess)
        }
    
    private func sizeIf(pointSizeToTry:CGFloat)->(CGFloat)
        {
        let s:CGFloat = text!.sizeWithAttributes(
            [NSFontAttributeName: font.fontWithSize(pointSizeToTry)] )
            .height
        
        print("guessing .. ", pointSizeToTry, " .. " , s)
        return s
        }
    
    private func tweak()
        {
        let ats = NSMutableAttributedString(string: self.text!)
        let rg = NSRange(location: 0, length: self.text!.characters.count)
        
        ats.addAttribute(
            NSKernAttributeName, value:CGFloat(tracking), range:rg )
                    
        ats.addAttribute(
            NSExpansionAttributeName, value:CGFloat(stretching), range:rg )
        
        self.attributedText = ats
        }
    }

哇,这个完美地运行了 - 谢谢Joe!而对于Swift 3版本的代码 - 你只需要进行Xcode建议的编辑即可。 - Mohit Singh
界面构建器在Xcode 9.2中与此代码存在问题。 - Vyachaslav Gerchicov

9

在viewWillAppear中调用一行代码即可解决问题:

testLabel.font = testLabel.font.fontWithSize(testLabel.frame.height * 2/3)

在Storyboard中,我将所有标签的高度设置为相对于视图整体高度的比例,这使得字体大小能够随它们动态缩放。
注意到字体大小实际上是标签高度的2/3。如果您使用的字体有下沉到线下的尾巴(如y、g、q、p或j),则需要将字号设置为标签高度的比例,以免截断这些尾巴。Helvetica Neue适用于2/3的设置,但根据使用的字体尝试其他比例。对于没有尾巴、数字或全大写文本的字体,1:1的比例可能就足够了。

1
这对我有用。虽然我选择了更接近1/2(而不是2/3)的东西。谢谢! - xta

6

基于@Conaaando的出色答案,我更新了一个包含IBDesignable参数的版本,这样就可以在接口构建器中编辑它:

输入图像描述

代码如下:

//
//  TIFFitToHeightLabel.swift
//

import Foundation
import UIKit

@IBDesignable class TIFFitToHeightLabel: UILabel {

    @IBInspectable var minFontSize:CGFloat = 12 {
        didSet {
            font = fontToFitHeight()
        }
    }

    @IBInspectable var maxFontSize:CGFloat = 30 {
        didSet {
            font = fontToFitHeight()
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        font = fontToFitHeight()
    }

    // Returns an UIFont that fits the new label's height.
    private func fontToFitHeight() -> UIFont {

        var minFontSize: CGFloat = self.minFontSize
        var maxFontSize: CGFloat = self.maxFontSize
        var fontSizeAverage: CGFloat = 0
        var textAndLabelHeightDiff: CGFloat = 0

        while (minFontSize <= maxFontSize) {
            fontSizeAverage = minFontSize + (maxFontSize - minFontSize) / 2

            if let labelText: NSString = text {
                let labelHeight = frame.size.height

                let testStringHeight = labelText.sizeWithAttributes(
                    [NSFontAttributeName: font.fontWithSize(fontSizeAverage)]
                    ).height

                textAndLabelHeightDiff = labelHeight - testStringHeight

                if (fontSizeAverage == minFontSize || fontSizeAverage == maxFontSize) {
                    if (textAndLabelHeightDiff < 0) {
                        return font.fontWithSize(fontSizeAverage - 1)
                    }
                    return font.fontWithSize(fontSizeAverage)
                }

                if (textAndLabelHeightDiff < 0) {
                    maxFontSize = fontSizeAverage - 1

                } else if (textAndLabelHeightDiff > 0) {
                    minFontSize = fontSizeAverage + 1

                } else {
                    return font.fontWithSize(fontSizeAverage)
                }
            }
        }
        return font.fontWithSize(fontSizeAverage)
    }
}

4

结合@DGund和@Kashif的答案,这里提供一个简单的IB方案:

enter image description here

这个方案通过自动缩小参数可以达到你指定的最低高度来适应文本。


4

这部分内容大量借鉴了Joel Fischer的答案。他的答案仅考虑标签的高度 - 我进行了一些更改,同时考虑了标签的宽度(给定输入字符串),这正是我想要的:

typedef enum
{
    kDimensionHeight,
    kDimensionWidth,
} DimensionType;

@implementation UIFont (AdaptiveFont)

+ (UIFont *)_adaptiveFontWithName:(NSString *)fontName minSize:(NSInteger)minSize labelDimension:(CGFloat)labelDimension testString:(NSString *)testString dimension:(DimensionType)dimension
{
    UIFont *tempFont = nil;
    NSInteger tempMin = minSize;
    NSInteger tempMax = 256;
    NSInteger mid = 0;
    NSInteger difference = 0;
    CGFloat testStringDimension = 0.0;

    while (tempMin <= tempMax) {
        @autoreleasepool {
            mid = tempMin + (tempMax - tempMin) / 2;
            tempFont = [UIFont fontWithName:fontName size:mid];

            // determine dimension to test
            if (dimension == kDimensionHeight) {
                testStringDimension = [testString sizeWithFont:tempFont].height;
            } else {
                testStringDimension = [testString sizeWithFont:tempFont].width;
            }
            difference = labelDimension - testStringDimension;

            if (mid == tempMin || mid == tempMax) {
                if (difference < 0) {
                    return [UIFont fontWithName:fontName size:(mid - 1)];
                }
                return [UIFont fontWithName:fontName size:mid];
            }

            if (difference < 0) {
                tempMax = mid - 1;
            } else if (difference > 0) {
                tempMin = mid + 1;
            } else {
                return [UIFont fontWithName:fontName size:mid];
            }
        }
    }
    return [UIFont fontWithName:fontName size:mid];
}

+ (UIFont *)adaptiveFontWithName:(NSString *)fontName minSize:(NSInteger)minSize labelSize:(CGSize)labelSize string:(NSString *)string
{
    UIFont *adaptiveFont = nil;
    NSString *testString = nil;

    // get font, given a max height
    testString = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    UIFont *fontConstrainingHeight = [UIFont _adaptiveFontWithName:fontName minSize:minSize labelDimension:labelSize.height testString:testString dimension:kDimensionHeight];
    CGSize boundsConstrainingHeight = [string sizeWithFont:fontConstrainingHeight];
    CGSize boundsConstrainingWidth = CGSizeZero;

    // if WIDTH is fine (while constraining HEIGHT), return that font
    if (boundsConstrainingHeight.width <= labelSize.width) {
        adaptiveFont = fontConstrainingHeight;
    } else {
        // get font, given a max width
        // i.e., fontConstrainingWidth
        testString = string;
        adaptiveFont = [UIFont _adaptiveFontWithName:fontName minSize:minSize labelDimension:labelSize.width testString:testString dimension:kDimensionWidth];

        // TEST comparison
        boundsConstrainingWidth = [string sizeWithFont:adaptiveFont];
    }
    return adaptiveFont;
}

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