Search code examples
iosswiftxcodefontsuikit

UILabel and UIButton not respecting minimum font size despite setting adjustsFontSizeToFitWidth and minimumScaleFactor


I'm working on an iOS project using Swift, and I'm trying to ensure that the font size in both UILabel and UIButton automatically scales down when the text doesn't fit. However, despite setting adjustsFontSizeToFitWidth and minimumScaleFactor, the font size doesn't seem to change at all.

struct UIStyling {
    static func styleLabel(label: UILabel, textLabel: String) {
        let fontName = "Montserrat-Medium"
        let fontSize: CGFloat = 18
        let minimumFontSize: CGFloat = 6

        guard let font = UIFont(name: fontName, size: fontSize) else {
            fatalError("Failed to load font \(fontName).")
        }

        let kernValue = fontSize * 0.2
        let textColor = UIColor(hex: "FFFFFF")

        label.numberOfLines = 0
        label.textAlignment = .center
        label.lineBreakMode = .byWordWrapping
        label.adjustsFontSizeToFitWidth = true
        label.minimumScaleFactor = minimumFontSize / fontSize

        let labelAttributedString = NSMutableAttributedString(
            string: textLabel,
            attributes: [
                .font: font,
                .kern: kernValue,
                .foregroundColor: textColor
            ]
        )

        label.attributedText = labelAttributedString
    }

    static func styleButton(
        _ button: UIButton,
        title: String,
        fontName: String = "Montserrat-Medium",
        fontSize: CGFloat = 18,
        minimumFontSize: CGFloat = 6,
        cornerRadius: CGFloat = 20,
        backgroundColor: UIColor,
        titleColor: UIColor
    ) {
        guard let font = UIFont(name: fontName, size: fontSize) else {
            fatalError("Failed to load font \(fontName).")
        }

        button.titleLabel?.adjustsFontSizeToFitWidth = true
        button.titleLabel?.minimumScaleFactor = minimumFontSize / fontSize
        button.titleLabel?.numberOfLines = 0
        button.titleLabel?.lineBreakMode = .byClipping

        button.setTitle(title, for: .normal)
        button.titleLabel?.font = font
        button.setTitleColor(titleColor, for: .normal)

        button.backgroundColor = backgroundColor
        button.layer.cornerRadius = cornerRadius
    }
}

func buttonStyles(for sceneType: SceneType) -> (fontName: String, backgroundColors: [(UIColor, UIColor)], textColors: (UIColor, UIColor)) {
    switch sceneType {
    case .egypt:
        return (
            fontName: "Montserrat-Medium",
            backgroundColors: [
                (UIColor(hex: "#FFD700").withAlphaComponent(0.8), UIColor(hex: "#333333")),
                (UIColor(hex: "#006400").withAlphaComponent(0.8), UIColor(hex: "#FFFFFF"))
            ],
            textColors: (UIColor(hex: "#333333"), UIColor(hex: "#FFFFFF"))
        )
    case .future:
        return (
            fontName: "Montserrat-Medium",
            backgroundColors: [
                (UIColor(hex: "#1E90FF").withAlphaComponent(0.8), UIColor(hex: "#FFFFFF")),
                (UIColor(hex: "#D3D0D0").withAlphaComponent(0.8), UIColor(hex: "#090909"))
            ],
            textColors: (UIColor(hex: "#FFFFFF"), UIColor(hex: "#090909"))
        )
    case .main:
        return (
            fontName: "Montserrat-Medium",
            backgroundColors: [
                (UIColor(hex: "#FFD700").withAlphaComponent(0.8), UIColor(hex: "#333333")),
                (UIColor(hex: "#1E90FF").withAlphaComponent(0.8), UIColor(hex: "#FFFFFF"))
            ],
            textColors: (UIColor(hex: "#333333"), UIColor(hex: "#FFFFFF"))
        )
    }
}

func updateUI() {
    let currentScene = storyBrain.getCurrentScene()

    UIStyling.styleLabel(label: textLabel, textLabel: currentScene.title)

    let styles = buttonStyles(for: currentScene.sceneType)

    UIStyling.styleButton(button1,
                          title: currentScene.button1,
                          fontName: styles.fontName,
                          backgroundColor: styles.backgroundColors[0].0,
                          titleColor: styles.textColors.0)

    UIStyling.styleButton(button2,
                          title: currentScene.button2,
                          fontName: styles.fontName,
                          backgroundColor: styles.backgroundColors[1].0,
                          titleColor: styles.textColors.1)

    button1.layer.cornerRadius = Style.cornerRadius
    button2.layer.cornerRadius = Style.cornerRadius

    background.image = UIImage(named: currentScene.backgroundImage)

    print("Label size: \(textLabel.frame.size)")
    print("Minimum scale factor: \(textLabel.minimumScaleFactor)")
    print("Font size: \(textLabel.font.pointSize)")
}
func updateUI() {
    let currentScene = storyBrain.getCurrentScene()

    UIStyling.styleLabel(label: textLabel, textLabel: currentScene.title)

    let styles = buttonStyles(for: currentScene.sceneType)

    UIStyling.styleButton(button1,
                          title: currentScene.button1,
                          fontName: styles.fontName,
                          backgroundColor: styles.backgroundColors[0].0,
                          titleColor: styles.textColors.0)

    UIStyling.styleButton(button2,
                          title: currentScene.button2,
                          fontName: styles.fontName,
                          backgroundColor: styles.backgroundColors[1].0,
                          titleColor: styles.textColors.1)
    
    button1.layer.cornerRadius = Style.cornerRadius
    button2.layer.cornerRadius = Style.cornerRadius

    background.image = UIImage(named: currentScene.backgroundImage)

    print("Label size: \(textLabel.frame.size)")
    print("Minimum scale factor: \(textLabel.minimumScaleFactor)")
    print("Font size: \(textLabel.font.pointSize)")

    // ...
}

Problem Description:

  • Issue: The font size in both UILabel and UIButton does not change, even if the text is too long to fit.
  • Expected Behavior: When the text doesn't fit, the font size should scale down according to the minimumScaleFactor setting.
  • Current Behavior: The font size remains the same, ignoring the adjustsFontSizeToFitWidth and minimumScaleFactor properties.

What I've Tried:

  1. Setting adjustsFontSizeToFitWidth and minimumScaleFactor programmatically: As shown in the code above, I've set these properties, but the font size does not scale down.
  2. Testing with different minimumFontSize values: I tried different values for the minimumFontSize, but it didn't make any difference.
  3. Verifying font loading: I confirmed that the fonts are correctly loaded and applied.
  4. Checking Auto Layout constraints: The labels and buttons have enough room, and the constraints seem fine.

Questions:

  1. Why is the minimumScaleFactor being ignored? Is there something in my code that could be preventing this from working?
  2. Is there a specific setting in Interface Builder or programmatically that I might be missing? Could this be a constraint issue?
  3. Could this be related to a bug in the iOS version I'm targeting? Or is there something wrong with the way I'm applying styles?

Environment:

  • Xcode Version: 15.4 (15F31d)
  • Swift Version: 5.10
  • iOS Version: minimum Deployments - 15.3

Solution

  • To get auto-font-scaling, .lineBreakMode has to be set to clipping or one of the truncating modes - Head, Middle, or (most commonly) Tail:

    //label.lineBreakMode = .byWordWrapping
    label.lineBreakMode = .byTruncatingTail
        
    

    Screenshot of UILabel test font autoscaling