I have a method for creating an auto-scaling font based on Dynamic Type that looks like so:
extension UIFont {
public static func getAutoScalingFont(_ fontName: String, _ textStyle: UIFont.TextStyle) -> UIFont {
// getFontSize pulls from a map of UIFont.TextStyle and UIFont.Weight to determine the appropriate point size
let size = getFontSize(forTextStyle: textStyle)
guard let font = UIFont(name: fontName.rawValue, size: size) else {
return UIFont.systemFont(ofSize: size)
}
let fontMetrics = UIFontMetrics(forTextStyle: textStyle)
let traitCollection = UITraitCollection(preferredContentSizeCategory: UIApplication.shared.preferredContentSizeCategory)
let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle, compatibleWith: traitCollection)
return fontMetrics.scaledFont(for: font, maximumPointSize: fontDescriptor.pointSize)
}
}
This seems to work great; when I change the Text Size slider value in the phone Settings, the font scales as necessary.
I'm now trying to add the logic for a minimum UIContentSizeCategory. That is, if the user sets their Text Size value to be less than my specified minimum size category, the font should scale as if they've selected the minimum value.
Here's my attempt:
extension UIFont {
// This variable represents the minimum size category I want to support; that is, if the user
// chooses a size category smaller than .large, fonts should be scaled to the .large size
private static let minimumSupportedContentSize: UIContentSizeCategory = .large
public static func getAutoScalingFont(_ fontName: String, _ textStyle: UIFont.TextStyle) -> UIFont {
let size = getFontSize(forTextStyle: textStyle)
guard let font = UIFont(name: fontName.rawValue, size: size) else {
return UIFont.systemFont(ofSize: size)
}
// I've extended UIContentSizeCategory to adhere to Comparable so this works fine
let contentSize = max(UIApplication.shared.preferredContentSizeCategory, minimumSupportedContentSize)
let fontMetrics = UIFontMetrics(forTextStyle: textStyle)
let traitCollection = UITraitCollection(preferredContentSizeCategory: contentSize)
let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle, compatibleWith: traitCollection)
return fontMetrics.scaledFont(for: font, maximumPointSize: fontDescriptor.pointSize)
}
}
Via logs I'm able to tell that, as expected, the contentSize
I pass into the UITraitCollection initializer is never a value smaller than .large
. However, it seems like the value passed to that initializer represents a maximum content size category. That is, if I init the trait collection like so:
let traitCollection = UITraitCollection(preferredContentSizeCategory: .large)
the font will re-scale for all UIContentSizeCategory's smaller than .large
but will not re-scale for any categories larger than .large
.
Does anyone know how to accomplish setting a minimum UIContentSizeCategory?
Although we have minimumContentSizeCategory
and maximumContentSizeCategory
supported from iOS 15, we still need the older way in a few scenarios. For example, these 2 properties doesn't work when we need to support dynamic text styles in NSAttributedString
.
Here is how I did it the older way,
Use UIApplication.shared.preferredContentSizeCategory
to decide which preferredContentSizeCategory
to use with UITraitCollection
Example:
func getPreferredFont(textStyle: UIFont.TextStyle, weight: UIFont.Weight? = nil) -> UIFont {
let preferredContentSizeCategory: UIContentSizeCategory
switch UIApplication.shared.preferredContentSizeCategory {
case .extraSmall, .small, .medium:
preferredContentSizeCategory = .large
case .accessibilityExtraLarge, .accessibilityExtraExtraLarge, .accessibilityExtraExtraExtraLarge:
preferredContentSizeCategory = .accessibilityLarge
default:
preferredContentSizeCategory = UIApplication.shared.preferredContentSizeCategory
}
let traitCollection = UITraitCollection(preferredContentSizeCategory: preferredContentSizeCategory)
let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle, compatibleWith: traitCollection)
let fontMetrics = UIFontMetrics(forTextStyle: textStyle)
let font: UIFont
if let weight = weight {
font = UIFont.systemFont(ofSize: fontDescriptor.pointSize, weight: weight)
} else {
font = UIFont.systemFont(ofSize: fontDescriptor.pointSize)
}
return fontMetrics.scaledFont(for: font, maximumPointSize: fontDescriptor.pointSize, compatibleWith: traitCollection)
}