UIButton not respecting maximumContentSizeCategory

I have a UIButton inside a dynamically-populated stack view that is within a self-sizing table view cell, and although it has a maximumContentSizeCategory of XXXL it is clearly not respecting that. Has anyone else run into this issue, and if so have you found a workaround?

I used the appliedContentSizeCategoryLimitsDescription property described here: https://useyourloaf.com/blog/restricting-dynamic-type-sizes to confirm that the maximumContentSizeCategory is indeed being set correctly.

This is in the iOS 16.2 iPhone 14 Pro simulator, running in Xcode 14.2.

  • Can you please add the code you are using to create and configure your button?

Add a Comment

Replies

@Rincewind Unfortunately I'm unable to post the code publicly, but in trying to reduce the issue I've only been able to do so by setting the maximumContentSizeCategory of the cell itself (in cellForRowAtIndexPath) vs. setting it on the view controller's root view in viewDidLoad. I'm still trying to figure out why that is.

@Rincewind Here's code for reproducing the issue by setting the maximum size on the cell:

class ViewController: UIViewController {
    func addButtons(to stack: UIStackView) {
        let titles = ["Button 1", "Button 2", "Button 3"]

        for title in titles {
            let button = button(title: title)
            let heightConstraint = NSLayoutConstraint(
                item: button,
                attribute: NSLayoutConstraint.Attribute.height,
                relatedBy: NSLayoutConstraint.Relation.greaterThanOrEqual,
                toItem: nil,
                attribute: NSLayoutConstraint.Attribute.notAnAttribute,
                multiplier: 1,
                constant: 48
            )
            button.addConstraint(heightConstraint)
            stack.addArrangedSubview(button)
        }
    }

    func button(title: String) -> UIButton {
        var configuration = UIButton.Configuration.plain()
        configuration.titleAlignment = .leading
        configuration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
            var outgoing = incoming
            outgoing.foregroundColor = UIColor.blue
            outgoing.font = UIFont.preferredFont(forTextStyle: .body)

            return outgoing
        }

        let button = UIButton(configuration: configuration)
        button.setTitle(title, for: .normal)
        return button
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TextCell else { return UITableViewCell() }

        cell.maximumContentSizeCategory = .extraLarge
        addButtons(to: cell.stackView)
        return cell
    }
}

class TextCell: UITableViewCell {
    @IBOutlet var stackView: UIStackView!
}

This is your issue:

        configuration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { incoming in
            var outgoing = incoming
            outgoing.foregroundColor = UIColor.blue
            outgoing.font = UIFont.preferredFont(forTextStyle: .body) // this uses the global preferredContentSize

            return outgoing
        }

When you create the new font, your using the current global content size to create it, rather than the one that is actually on the button, hence when it goes outside of the range of the button you end up with larger (or smaller) text than you expected.

I consider this an unfortunate issue on our part – we don't make this easy to resolve because we don't trivially provide the information you need in the closure to resolve it. You can look at the current font to see what font size was resolved and generate your font replacement at that size, or you can update the closure using a configuration update handler.