👨‍💻 iOS 项目中使用自定义字体

🪴 全局加载字体

1. 导入字体文件

准备好要使用的字体文件,并且将它导入至你的 XCode Project 中。

Tips:一定要勾选 Copy items if needed ,不勾选的话只会进行位置引用。


2. 修改 Info.plist

打开你的 Info.plist 文件,新增一项:Fonts provided by application

对应的值即为你的字体文件的名称 (不需要带 Group 前缀,需要带文件后缀)。


3. 添加资源文件

确保你的字体文件已添加至 TARGETS - Build Phases - Copy Bundle Resources 中。

一般导入字体文件后会自动添加,但也会有意外 😃


4. 确认导入成功

运行这段代码即可打印出所有可用字体,检查是否导入成功。

for fontFamily in UIFont.familyNames {
    print(fontFamily)

    for font in UIFont.fontNames(forFamilyName: fontFamily) {
        print(fontFamily + ": " + font)
    }
}

🧐 覆盖默认字体

如果你需要全局都显示某个字体,那么就需要这样设置。

如果不这样做,那么你需要在每一个显示文本的地方都指定 FontName

Tips:使用覆盖 UIFont 的方式设置全局字体会导致键盘等系统 UI 字体也被替换。

1. 创建 UIFont 拓展

Extension Group 下创建 UIFontExtension 文件拓展 UIFont 类。

当然,你也可以放在别的位置,使用其他名称 🫠。

import UIKit

// MARK: - AppFontName
struct AppFontName {
    static let italic = "Montserrat-Medium"
    static let regular = "Montserrat-Medium"
    static let semibold = "Montserrat-SemiBold"
    static let bold = "Montserrat-Bold"
    static let heavy = "Montserrat-ExtraBold"
    static let black = "Montserrat-Black"
}

// MARK: - UIFontDescriptor.AttributeName
extension UIFontDescriptor.AttributeName {

    /// NSCTFontUIUsageAttribute
    static let nsctFontUIUsage = UIFontDescriptor.AttributeName(rawValue: "NSCTFontUIUsageAttribute")

}

/// 字体扩展
extension UIFont {

    /// 是否已经替换过
    static var isOverrided: Bool = false

    // 重写系统字体
    @objc class func mySystemFont(ofSize size: CGFloat, weight: UIFont.Weight) -> UIFont {
        switch weight {
        case .ultraLight, .thin, .light, .regular, .medium:
            return UIFont(name: AppFontName.regular, size: size)!
        case .semibold:
            return UIFont(name: AppFontName.semibold, size: size)!
        case .bold:
            return UIFont(name: AppFontName.bold, size: size)!
        case .heavy:
            return UIFont(name: AppFontName.heavy, size: size)!
        case .black:
            return UIFont(name: AppFontName.black, size: size)!
        default:
            return UIFont(name: AppFontName.regular, size: size)!
        }
    }

    // 重写粗体字体
    @objc class func myBoldSystemFont(ofSize size: CGFloat) -> UIFont {
        return UIFont(name: AppFontName.bold, size: size)!
    }

    // 重写斜体字体
    @objc class func myItalicSystemFont(ofSize size: CGFloat) -> UIFont {
        return UIFont(name: AppFontName.italic, size: size)!
    }

    // 重写字体的编码方法
    @objc convenience init(myCoder aDecoder: NSCoder) {
        guard
                let fontDescriptor = aDecoder.decodeObject(forKey: "UIFontDescriptor") as? UIFontDescriptor,
                let fontAttribute = fontDescriptor.fontAttributes[.nsctFontUIUsage] as? String
        else {
            self.init(myCoder: aDecoder)
            return
        }
        var fontName = ""
        switch fontAttribute {
        case "CTFontRegularUsage":
            fontName = AppFontName.regular
        case "CTFontEmphasizedUsage", "CTFontBoldUsage":
            fontName = AppFontName.bold
        case "CTFontObliqueUsage":
            fontName = AppFontName.italic
        default:
            fontName = AppFontName.regular
        }
        self.init(name: fontName, size: fontDescriptor.pointSize)!
    }

    /// 替换系统字体
    class func overrideInitialize() {

        // 避免 method swizzling 运行两次
        guard self == UIFont.self, !isOverrided else {
            return
        }

        // 避免 method swizzling 运行两次并恢复到原始初始化函数
        isOverrided = true

        // 替换系统字体
        if let systemFontMethod = class_getClassMethod(self, #selector(systemFont(ofSize:weight:))),
           let mySystemFontMethod = class_getClassMethod(self, #selector(mySystemFont(ofSize:weight:))) {
            method_exchangeImplementations(systemFontMethod, mySystemFontMethod)
        }

        // 替换粗体字体
        if let boldSystemFontMethod = class_getClassMethod(self, #selector(boldSystemFont(ofSize:))),
           let myBoldSystemFontMethod = class_getClassMethod(self, #selector(myBoldSystemFont(ofSize:))) {
            method_exchangeImplementations(boldSystemFontMethod, myBoldSystemFontMethod)
        }

        // 替换斜体字体
        if let italicSystemFontMethod = class_getClassMethod(self, #selector(italicSystemFont(ofSize:))),
           let myItalicSystemFontMethod = class_getClassMethod(self, #selector(myItalicSystemFont(ofSize:))) {
            method_exchangeImplementations(italicSystemFontMethod, myItalicSystemFontMethod)
        }

        // Trick to get over the lack of UIFont.init(coder:))
        if let initCoderMethod = class_getInstanceMethod(self, #selector(UIFontDescriptor.init(coder:))),
           let myInitCoderMethod = class_getInstanceMethod(self, #selector(UIFont.init(myCoder:))) {
            method_exchangeImplementations(initCoderMethod, myInitCoderMethod)
        }
    }
}

2. 覆盖初始化方法

在你的 AppDelegate 文件中注册如下方法,覆盖 UIFont 初始化。

override init() {
    super.init()
    UIFont.overrideInitialize()
}

🫠 Reference Links