I tried two ways:
label.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
label.adjustsFontForContentSizeCategory = true
This works fine, even when the preferred text size is changed in the Settings, the text size changes automatically, even before when I go back to the app. But it only works with the system font (San Francisco).
To use a custom font, I add an extension to UIFontDescriptor
:
//from this answer http://stackoverflow.com/a/35467158/2907715
extension UIFontDescriptor {
private struct SubStruct {
static var preferredFontName: String = "Avenir-medium"
}
static let fontSizeTable : NSDictionary = [
UIFontTextStyle.headline: [
UIContentSizeCategory.accessibilityExtraExtraExtraLarge: 23,
UIContentSizeCategory.accessibilityExtraExtraLarge: 23,
UIContentSizeCategory.accessibilityExtraLarge: 23,
UIContentSizeCategory.accessibilityLarge: 23,
UIContentSizeCategory.accessibilityMedium: 23,
UIContentSizeCategory.extraExtraExtraLarge: 23,
UIContentSizeCategory.extraExtraLarge: 21,
UIContentSizeCategory.extraLarge: 19,
UIContentSizeCategory.large: 17,
UIContentSizeCategory.medium: 16,
UIContentSizeCategory.small: 15,
UIContentSizeCategory.extraSmall: 14
],
UIFontTextStyle.subheadline: [
UIContentSizeCategory.accessibilityExtraExtraExtraLarge: 21,
UIContentSizeCategory.accessibilityExtraExtraLarge: 21,
UIContentSizeCategory.accessibilityExtraLarge: 21,
UIContentSizeCategory.accessibilityLarge: 21,
UIContentSizeCategory.accessibilityMedium: 21,
UIContentSizeCategory.extraExtraExtraLarge: 21,
UIContentSizeCategory.extraExtraLarge: 19,
UIContentSizeCategory.extraLarge: 17,
UIContentSizeCategory.large: 15,
UIContentSizeCategory.medium: 14,
UIContentSizeCategory.small: 13,
UIContentSizeCategory.extraSmall: 12
],
UIFontTextStyle.body: [
UIContentSizeCategory.accessibilityExtraExtraExtraLarge: 53,
UIContentSizeCategory.accessibilityExtraExtraLarge: 47,
UIContentSizeCategory.accessibilityExtraLarge: 40,
UIContentSizeCategory.accessibilityLarge: 33,
UIContentSizeCategory.accessibilityMedium: 28,
UIContentSizeCategory.extraExtraExtraLarge: 23,
UIContentSizeCategory.extraExtraLarge: 21,
UIContentSizeCategory.extraLarge: 19,
UIContentSizeCategory.large: 17,
UIContentSizeCategory.medium: 16,
UIContentSizeCategory.small: 15,
UIContentSizeCategory.extraSmall: 14
],
UIFontTextStyle.caption1: [
UIContentSizeCategory.accessibilityExtraExtraExtraLarge: 18,
UIContentSizeCategory.accessibilityExtraExtraLarge: 18,
UIContentSizeCategory.accessibilityExtraLarge: 18,
UIContentSizeCategory.accessibilityLarge: 18,
UIContentSizeCategory.accessibilityMedium: 18,
UIContentSizeCategory.extraExtraExtraLarge: 18,
UIContentSizeCategory.extraExtraLarge: 16,
UIContentSizeCategory.extraLarge: 14,
UIContentSizeCategory.large: 12,
UIContentSizeCategory.medium: 11,
UIContentSizeCategory.small: 11,
UIContentSizeCategory.extraSmall: 11
],
UIFontTextStyle.caption2: [
UIContentSizeCategory.accessibilityExtraExtraExtraLarge: 17,
UIContentSizeCategory.accessibilityExtraExtraLarge: 17,
UIContentSizeCategory.accessibilityExtraLarge: 17,
UIContentSizeCategory.accessibilityLarge: 17,
UIContentSizeCategory.accessibilityMedium: 17,
UIContentSizeCategory.extraExtraExtraLarge: 17,
UIContentSizeCategory.extraExtraLarge: 15,
UIContentSizeCategory.extraLarge: 13,
UIContentSizeCategory.large: 11,
UIContentSizeCategory.medium: 11,
UIContentSizeCategory.small: 11,
UIContentSizeCategory.extraSmall: 11
],
UIFontTextStyle.footnote: [
UIContentSizeCategory.accessibilityExtraExtraExtraLarge: 19,
UIContentSizeCategory.accessibilityExtraExtraLarge: 19,
UIContentSizeCategory.accessibilityExtraLarge: 19,
UIContentSizeCategory.accessibilityLarge: 19,
UIContentSizeCategory.accessibilityMedium: 19,
UIContentSizeCategory.extraExtraExtraLarge: 19,
UIContentSizeCategory.extraExtraLarge: 17,
UIContentSizeCategory.extraLarge: 15,
UIContentSizeCategory.large: 13,
UIContentSizeCategory.medium: 12,
UIContentSizeCategory.small: 12,
UIContentSizeCategory.extraSmall: 12
],
]
final class func preferredDescriptor(textStyle: String) -> UIFontDescriptor {
let contentSize = UIApplication.shared.preferredContentSizeCategory
let style = fontSizeTable[textStyle] as! NSDictionary
return UIFontDescriptor(name: SubStruct.preferredFontName, size: CGFloat((style[contentSize] as! NSNumber).floatValue))
}
}
and in viewDidLoad()
:
label.font = UIFont(descriptor: UIFontDescriptor.preferredDescriptor(textStyle: UIFontTextStyle.body.rawValue), size: 0)
NotificationCenter.default.addObserver(self, selector:#selector(self.userChangedTextSize(notification:)), name: NSNotification.Name.UIContentSizeCategoryDidChange, object: nil)
here is the userChangedTextSize
function:
func userChangedTextSize(notification: NSNotification) {
label.font = UIFont(descriptor: UIFontDescriptor.preferredDescriptor(textStyle: UIFontTextStyle.body.rawValue), size: 0)
}
The problem with this method is that the text size won't change until the user goes back to the app, and then the user would see the old text size change to the new size, which is not ideal.
Could I have the best of both worlds: a custom font with a size that changes automatically in the background?
SwiftUI comes with support for all of Dynamic Type's font sizes, all set using the . font() modifier. However, if you ask for a specific font and size, you'll find your text no longer scales up or down automatically according to the user's Dynamic Type settings – it remains fixed.
February 11, 2021. The Dynamic Type feature allows users to choose the size of textual content displayed on the screen. It helps users who need larger text for better readability. It also accommodates those who can read smaller text, allowing more information to appear on the screen.
To use a custom font, add the font file that contains your licensed font to your app, and then apply the font to a text view or set it as a default font within a container view. SwiftUI's adaptive text display scales the font automtically using Dynamic Type.
The problem with this method is that the text size won't change until the user goes back to the app, and then the user would see the old text size change to the new size, which is not ideal.
I share your thoughts that this would probably be a better UX, but I guess you are overthinking it a bit.
If you have a look at system provided Apps (e.g. Contacts) you will clearly see that the refresh is not done until the user goes back to the app, too.
By the way, you could refactor your code a bit for Swift 3:
extension UIFontDescriptor {
private struct SubStruct {
static var preferredFontName: String = "Avenir-medium"
}
static let fontSizeTable: [UIFontTextStyle: [UIContentSizeCategory: CGFloat]] = [
.headline: [
.accessibilityExtraExtraExtraLarge: 23,
.accessibilityExtraExtraLarge: 23,
.accessibilityExtraLarge: 23,
.accessibilityLarge: 23,
.accessibilityMedium: 23,
.extraExtraExtraLarge: 23,
.extraExtraLarge: 21,
.extraLarge: 19,
.large: 17,
.medium: 16,
.small: 15,
.extraSmall: 14
],
.subheadline: [
.accessibilityExtraExtraExtraLarge: 21,
.accessibilityExtraExtraLarge: 21,
.accessibilityExtraLarge: 21,
.accessibilityLarge: 21,
.accessibilityMedium: 21,
.extraExtraExtraLarge: 21,
.extraExtraLarge: 19,
.extraLarge: 17,
.large: 15,
.medium: 14,
.small: 13,
.extraSmall: 12
],
.body: [
.accessibilityExtraExtraExtraLarge: 53,
.accessibilityExtraExtraLarge: 47,
.accessibilityExtraLarge: 40,
.accessibilityLarge: 33,
.accessibilityMedium: 28,
.extraExtraExtraLarge: 23,
.extraExtraLarge: 21,
.extraLarge: 19,
.large: 17,
.medium: 16,
.small: 15,
.extraSmall: 14
],
.caption1: [
.accessibilityExtraExtraExtraLarge: 18,
.accessibilityExtraExtraLarge: 18,
.accessibilityExtraLarge: 18,
.accessibilityLarge: 18,
.accessibilityMedium: 18,
.extraExtraExtraLarge: 18,
.extraExtraLarge: 16,
.extraLarge: 14,
.large: 12,
.medium: 11,
.small: 11,
.extraSmall: 11
],
.caption2: [
.accessibilityExtraExtraExtraLarge: 17,
.accessibilityExtraExtraLarge: 17,
.accessibilityExtraLarge: 17,
.accessibilityLarge: 17,
.accessibilityMedium: 17,
.extraExtraExtraLarge: 17,
.extraExtraLarge: 15,
.extraLarge: 13,
.large: 11,
.medium: 11,
.small: 11,
.extraSmall: 11
],
.footnote: [
.accessibilityExtraExtraExtraLarge: 19,
.accessibilityExtraExtraLarge: 19,
.accessibilityExtraLarge: 19,
.accessibilityLarge: 19,
.accessibilityMedium: 19,
.extraExtraExtraLarge: 19,
.extraExtraLarge: 17,
.extraLarge: 15,
.large: 13,
.medium: 12,
.small: 12,
.extraSmall: 12
]
]
final class func preferredDescriptor(textStyle: UIFontTextStyle) -> UIFontDescriptor {
let contentSize = UIApplication.shared.preferredContentSizeCategory
let style = fontSizeTable[textStyle]!
return UIFontDescriptor(name: SubStruct.preferredFontName, size: style[contentSize]!)
}
}
No need to cast to NSDictionary
or NSNumber
and get the floatValue
indirectly.
This way your call site can use the following, more readable code:
func userChangedTextSize(notification: NSNotification) {
label.font = UIFont(descriptor: .preferredDescriptor(textStyle: .body), size: 0)
}
Edit: As I am working on the same right now, I improved the above (on SO commonly seen solution) to something way easier.
import UIKIt
extension UIFont {
private struct CustomFont {
static var fontFamily = "Avenir"
}
/// Returns a bold version of `self`
public var bolded: UIFont {
return fontDescriptor.withSymbolicTraits(.traitBold)
.map { UIFont(descriptor: $0, size: 0) } ?? self
}
/// Returns an italic version of `self`
public var italicized: UIFont {
return fontDescriptor.withSymbolicTraits(.traitItalic)
.map { UIFont(descriptor: $0, size: 0) } ?? self
}
/// Returns a scaled version of `self`
func scaled(scaleFactor: CGFloat) -> UIFont {
let newDescriptor = fontDescriptor.withSize(fontDescriptor.pointSize * scaleFactor)
return UIFont(descriptor: newDescriptor, size: 0)
}
class func preferredCustomFont(forTextStyle textStyle: UIFontTextStyle) -> UIFont {
// we are using the UIFontDescriptor which is less expensive than creating an intermediate UIFont
let systemFontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle)
let customFontDescriptor = UIFontDescriptor.init(fontAttributes: [
UIFontDescriptorFamilyAttribute: CustomFont.fontFamily,
UIFontDescriptorSizeAttribute: systemFontDescriptor.pointSize // use the font size of the default dynamic font
])
// return font of new family with same size as the preferred system font
return UIFont(descriptor: customFontDescriptor, size: 0)
}
}
Usage
func userChangedTextSize(notification: NSNotification) {
label.font = UIFont.preferredCustomFont(forTextStyle: .headline)
// or in Bold / Italic:
// label.font = UIFont.preferredCustomFont(forTextStyle: .headline).bolded
// label.font = UIFont.preferredCustomFont(forTextStyle: .headline).italicized
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With