在 Swift 3/iOS10 中使用带有自定义字体的动态类型的更好方法

A better way to use Dynamic Type with a custom font in Swift 3/iOS10

我尝试了两种方法:

方法一:

label.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body)
label.adjustsFontForContentSizeCategory = true

即使在“设置”中更改了首选文本大小,文本大小也会自动更改,即使在我返回应用程序之前也是如此。 但是它只适用于系统字体 (San Francisco)。

方法二:

要使用自定义字体,我将扩展名添加到 UIFontDescriptor:

//from this answer 
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))
    }

}

并在 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)

这里是 userChangedTextSize 函数:

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.

我同意你的想法,即这可能是一个更好的用户体验,但我想你有点想多了。

如果您查看系统提供的应用程序(例如联系人),您会清楚地看到 刷新也没有完成,直到用户返回应用程序

顺便说一下,您可以为 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]!)
    }
}

无需转换为 NSDictionaryNSNumber 并间接获得 floatValue

这样您的调用站点就可以使用以下更易读的代码:

func userChangedTextSize(notification: NSNotification) {
    label.font = UIFont(descriptor: .preferredDescriptor(textStyle: .body), size: 0)

}

编辑:因为我现在正在做同样的事情,所以我将上面的内容(在如此常见的解决方案上)改进为更简单的方法。

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: [=12=], size: 0) } ?? self
    }

    /// Returns an italic version of `self`
    public var italicized: UIFont {
        return fontDescriptor.withSymbolicTraits(.traitItalic)
            .map { UIFont(descriptor: [=12=], 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)
    }

}

用法

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
}

Swift 4: 支持辅助功能的自定义缩放字体(德语 BITV)

//UIFont+CustomScaledFont.swift

import UIKit

extension UIFont {

    /// Scaled and styled version of any custom Font
    ///
    /// - Parameters:
    ///   - name: Name of the Font
    ///   - textStyle: The text style i.e Body, Title, ...
    /// - Returns: The scaled custom Font version with the given textStyle 
    static func scaledFont(name:String, textStyle: UIFont.TextStyle) -> UIFont {

        let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle)

        guard let customFont = UIFont(name: name, size: fontDescriptor.pointSize) else {
            fatalError("Failed to load the \(name) font.")
        }

        return UIFontMetrics.default.scaledFont(for: customFont)
    }
}

成功将自定义字体添加到您的项目后...

打印可用字体名称:

for family in UIFont.familyNames {
    for name in UIFont.fontNames(forFamilyName: family) {
        print(name)
    }
 }

设置示例:

myLabel.text = "My scaled custom Font"
myLabel.font = UIFont.scaledFont(name: "MyCustomFontName-Bold", textStyle: .title1)
myLabel.adjustsFontForContentSizeCategory = true   

使用辅助功能检查器 (MacOS) 进行测试

Fred 回答但在 Swift 5(我试图编辑原始 post 但编辑队列总是满的):

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: [=10=], size: 0) } ?? self
    }

    /// Returns an italic version of `self`
    public var italicized: UIFont {
        return fontDescriptor.withSymbolicTraits(.traitItalic)
            .map { UIFont(descriptor: [=10=], 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: UIFont.TextStyle) -> 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: [
            UIFontDescriptor.AttributeName.family: CustomFont.fontFamily,
            UIFontDescriptor.AttributeName.size: 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)
    }

}

Frederik's Winkelsdorf 版本的代码,但经过调整以使用两种不同的字体系列(默认字体系列和粗体)以及使用示例。

MyFontExtension.swift

import UIKit
extension UIFontDescriptor {
    private struct FontFamily {
        static var preferredFontNameRegular: String = "Montserrat-Regular"
        static var preferredFontNameBold: String = "Montserrat-Bold"
    }
    
    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: UIFont.TextStyle, styleBold: Bool = false) -> UIFontDescriptor {
        let contentSize = UIApplication.shared.preferredContentSizeCategory
                
        let fontFamily = styleBold ? FontFamily.preferredFontNameBold : FontFamily.preferredFontNameRegular
        
        guard let style = fontSizeTable[textStyle], let size = style[contentSize] else {
            return UIFontDescriptor(name: fontFamily, size: 16)
        }
        return UIFontDescriptor(name: fontFamily, size: size)
    }
}

myViewController.swift

myBoldLabel.font = UIFont(descriptor: .preferredDescriptor(textStyle: .body, styleBold: true), size: 0)

myNormalLabel.font = UIFont(descriptor: .preferredDescriptor(textStyle: .body), size: 0)