'Dynamic type for UIButton's attributeTitle

Anyone ever got dynamic type working for attributedTitle on UIButton? Consider the super simple code below:

let font = UIFont(name: "Helvetica", size: 14)!
let scaledFont = UIFontMetrics.default.scaledFont(for: font)

let button = UIButton(type: .custom)
button.titleLabel?.font = scaledFont
button.titleLabel?.adjustsFontForContentSizeCategory = true

let attributes: [NSAttributedString.Key: Any] = [ .font: scaledFont ]
let attributedText = NSAttributedString(string: "Press me", attributes: attributes)
button.setAttributedTitle(attributedText, for: .normal)

If I scale the font size up and down using Accessibility Inspector, the button's size and label text doesn’t scale properly.

If I just call button.setTitle() passing an ordinary string, though, dynamic type scaling works fine.

Using the same pattern for attributed text directly on a UILabel works fine… it just seems to be when I use attributed text for a UIButton’s title.

Any thoughts or suggestions would be awesome. Thanks

Edit: After a bit more poking, it looks like what is happening is that the text is trying to scale, but the button's width/height aren't growing with it. If I dial up dynamic type to the largest text size and then create the screen and proceed to shrink the font size, it works OK because the buttons width/height constraints are set to an initially large value. But if I start with a small dynamic type setting and grow larger, the button doesn't accommodate the text size change



Solution 1:[1]

Just in case anyone is following along, the key to resolving this is contained in the first para of @XLE_22's post but is quite subtle.

When calling setAttributedTitle() on the button, you need to pass the attributed string from the label. ie. myButton.titleLabel?.attributedText... passing the local variable attributedText does not work. I can't rationalise why this is the case though. Consider the following code:

myButton.titleLabel?.attributedText = attributedText
let labelAttributedText = myButton.titleLabel?.attributedText

myButton.setAttributedTitle(labelAttributedText, for: .normal)  // works
myButton.setAttributedTitle(attributedText, for: .normal)       // doesn't work

If I compare attributedText with labelAttributedText, I can see that the former is a NSConcreteAttributedString whereas the latter is a NSConcreteMutableAttributedString. Asking them if they are equal return true and a quick comparison of their attributes seems to suggest that they are indeed the same, but there must be something ever so slightly different.

I tried initially creating attributedText as a mutable attributed string, but that didn't help either. Very puzzling, but a huge thanks to @XLE_22 for the tip... I doubt I would have ever tried that specific trick on my own.

Solution 2:[2]

In Swift 5.5 & XCode 13

If you are using a custom font. Maybe this code will help you.

class DynamicSizeButton: UIButton {
func customStyle() {
    guard let currentFont = self.titleLabel?.font, let customFont = UIFont(name: currentFont.fontName, size: currentFont.pointSize) else {return}
    self.titleLabel?.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: customFont)
    self.titleLabel?.adjustsFontForContentSizeCategory = true
}

override func awakeFromNib() {
    super.awakeFromNib()
    customStyle()
   }
}

USE.....

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Craig Edwards
Solution 2 Chandan