ios 如何设置字体大小以填充 UILabel 高度?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8812192/
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 16:13:29  来源:igfitidea点击:

How to set font size to fill UILabel height?

iphoneobjective-ciosxcode

提问by wayneh

I've seen a bunch of examples for changing the size of a UILabel.

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

Here's what I'd like to do: Change the font size so that the text will be as large as possible within the new height.

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

Any clues?

有什么线索吗?

采纳答案by dgund

Edit: Check out Joel Fischer's great answerto programmatically obtain the correct size!

编辑:查看Joel Fischer 的精彩答案,以编程方式获得正确的尺寸!

You can set the font to automatically fill the size of a label, and optionally not go below a minimum font size. Just set adjustsFontSizeToFitWidthto YES.Check out the UILabel Class Referenceif you need more information.

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

Although the boolean is called "adjustsFontSizeToFitWidth," it really means the largest size for the height of the label, that will stay on one line of the label (or however many lines you specify).

尽管布尔值称为“adjustsFontSizeToFitWidth”,但它实际上表示标签高度的最大尺寸,它将保留在标签的一行(或您指定的任意多行)上。

回答by backslash-f

I had the very same problem and, thanks to this thread and Joel's algorithm, I could fix it. :-)

我遇到了同样的问题,多亏了这个线程和 Joel 的算法,我才能解决它。:-)

Below is my code in Swift. I'm in iOS 8 + Autolayout.

下面是我在 Swift 中的代码。我在 iOS 8 + 自动布局。

Problem:

问题:

  1. User inputs expenses:
  1. 用户投入费用:

123 app

123应用

  1. When users tap the 'check' button, a menu appears from bottom, pushing everything to the top of the screen (shrinking stuff, including the label):
  1. 当用户点击“检查”按钮时,会从底部出现一个菜单,将所有内容推到屏幕顶部(缩小内容,包括标签):

123 app

123应用

After the fix:

修复后:

123 app

123应用

Which is exactly what the designer had in mind... :)

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

xScope app :)

xScope 应用程序 :)

I subclassed UILabel and overrode layoutSubviews. Then each time the UILabel gets its size changed, the font size is recalculated:

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


回答by Kashif

There is a simpler solution. Just add below lines and magically, the label adjusts its font size to fit the height of the label too:

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

SWIFT 3:

快速 3:

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

回答by Joel Fischer

Here's how I did it, since DGund's answer didn't work for me, it fit the width, but I wanted it to fit the height.

这是我的做法,因为 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];
}

This will take a font name, a size (it doesn't have to be a UILabel, theoretically, but I always used it with a UILabel), and a minimum size (you could also use a max size, just replace the 256 with the max size parameter). This will essentially test every font size between the minimum and maximum font sizes and return the one that is at or just underneath the target height.

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

Usage is self explanatory, but looks like this:

用法是不言自明的,但看起来像这样:

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

You can also make this a class method category on UIFont (which is what I did).

您还可以将其设为 UIFont 上的类方法类别(我就是这样做的)。

EDIT: On suggestion, I removed the for loop and spent a little time making it more efficient with a Binary Search routine. I did several checks to make absolutely sure that the font will end up fitting within the label. In initial testing it appears to work.

编辑:根据建议,我删除了 for 循环,并花了一些时间使用二进制搜索例程提高效率。我做了几次检查以绝对确保字体最终适合标签。在最初的测试中,它似乎有效。

回答by Fjohn

to adapt the text according to the height of my label I have adapt Joel method to swift

根据我的标签的高度调整文本我已经将 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)
}

and I use it this way :

我这样使用它:

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

回答by Fattie

Good news,

好消息,

Performing a binary search is completely unnecessary!

执行二分查找是完全没有必要的!

You need only iterate (a couple of times) using a ratio search.

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

        guess = guess * ( desiredHeight / guessHeight )

Here's a full total IBDesignablesolution.

这是一个完整的整体IBDesignable解决方案。

Note: when working with designers or typographers, you will need to set the tracking / stretching for fonts. (It's absurd Apple do not include this.) StyledLabelalso includestracking / stretching.

注意:与设计师或印刷师合作时,您需要设置字体的跟踪/拉伸。(这是荒谬的,Apple 不包括这个。)StyledLabel还包括跟踪/拉伸。

StyledLabel.swift

StyledLabel.swift

It sets tracking, stretching, AND it sets the point size to match the view frame heighton all devices.

它设置跟踪、拉伸,并设置点大小以匹配所有设备上的视图帧高度

In storyboard: just make the frame of the UILabel, the height you want the text to be - end of story!

在故事板中:只需制作 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
        }
    }

回答by Edward Suczewski

One line called in viewWillAppear does the trick:

在 viewWillAppear 中调用的一行可以解决问题:

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

In storyboard, I set all of my label heights relative to the overall height of the view, and this allows the font size to scale dynamically with them.

在故事板中,我将所有标签高度设置为相对于视图的整体高度,这允许字体大小随它们动态缩放。

Notice that the font size is actually 2/3 the height of the label. If the font you are using has tails that dip below the line (as in y, g, q, p, or j), you will want to make the font size a ratio of the label height so that those tails aren't chopped off. 2/3 works well for Helvetica Neue, but try other ratios depending on the font you're using. For fonts without tails, numbers, or all-caps text, a 1:1 ratio may suffice.

请注意,字体大小实际上是标签高度的 2/3。如果您使用的字体尾部低于线条(如 y、g、q、p 或 j),您需要使字体大小与标签高度成比例,以便这些尾部不会被切碎离开。2/3 适用于 Helvetica Neue,但根据您使用的字体尝试其他比例。对于没有尾部、数字或全大写文本的字体,1:1 的比例可能就足够了。

回答by Antoine

Based on @Conaaando's great answer, I've updated it to a version with IBDesignable parameters included, which makes it possible to edit it throughout the Interface builder:

基于@Conaaando 的精彩回答,我已将其更新为包含 IBDesignable 参数的版本,这使得在整个界面构建器中对其进行编辑成为可能:

enter image description here

在此处输入图片说明

And the code:

和代码:

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

回答by markckim

This borrows heavily from Joel Fischer's answer. His answer takes into account label height only -- I've made some changes to take into account label width as well (given an input string), which I wanted:

这大量借鉴了 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;
}

回答by Denys Triasunov

Combining answers by @DGund and @Kashif, here's a simple IB solution:

结合@DGund 和@Kashif 的回答,这是一个简单的 IB 解决方案:

enter image description here

在此处输入图片说明

This fits text by height as low as you specify in Autoshrink parameter.

这将根据您在 Autoshrink 参数中指定的高度来适应文本。