Reputation: 2566
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?
Upvotes: 2
Views: 2043
Reputation: 24341
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)
}
Upvotes: 5
Reputation: 4555
Starting from iOS 15 you can set limits on the minimum and maximum sizes of dynamic type:
// UIKit
view.minimumContentSizeCategory = .medium
view.maximumContentSizeCategory = .accessibilityExtraLarge
// SwiftUI
ContentView()
.dynamicTypeSize(.medium ... .accessibility3) // No smaller than medium, no bigger than accessibility3
When you set one or both of these properties on a view it limits the dynamic type size for that view and any of its subviews.
Since these are properties of UIView they are also available on subclasses like UILabel
and UITextView
. This gives you fine grained control to limit an individual text element without affecting other text:
label.minimumContentSizeCategory = .large
Here’s a quote from the Apple engineer in the WWDC session:
Please do not use this API to unduly limit text size. These settings serve an extremely important function, and it’s paramount that your app’s functionality is all available, and everything is legible, to people using the highest text size setting.
Upvotes: 1