You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

iOS使用Method Swizzling替换系统字体:Xib视图不生效求助

解决Xib/Storyboard中系统字体未被Method Swizzling替换的问题

这个问题我之前也踩过坑!核心原因是:Xib/Storyboard加载控件时,并不是直接调用你Swizzle的那些systemFont(ofSize:)类方法,而是通过UIFontDescriptor这条路径初始化字体——系统先创建对应系统字体的描述符,再用UIFont(descriptor:size:)生成字体对象,这部分你没做拦截,所以Xib里的字体没被替换。

下面是不用修改任何Xib的完整解决方案:

1. 补充Swizzle UIFont的初始化方法

我们需要拦截init(descriptor:size:)这个实例初始化方法,在里面把系统字体的描述符替换成自定义字体的描述符,同时保留原字体的字重、样式等属性。

修改你的UIFont扩展代码如下:

import UIKit
import ObjectiveC

extension UIFont {
    static let setSystemFont: Void = {
        // 保留你原来的所有类方法Swizzle代码
        let m1 = class_getClassMethod(UIFont.self, #selector(systemFont(ofSize:)))!
        let m2 = class_getClassMethod(UIFont.self, #selector(customFont(ofSize:)))!
        method_exchangeImplementations(m1, m2)
        
        let m3 = class_getClassMethod(UIFont.self, #selector(preferredFont(forTextStyle:)))!
        let m4 = class_getClassMethod(UIFont.self, #selector(customFont(forTextStyle:)))!
        method_exchangeImplementations(m3, m4)
        
        let m5 = class_getClassMethod(UIFont.self, #selector(boldSystemFont(ofSize:)))!
        let m6 = class_getClassMethod(UIFont.self, #selector(boldCustomFont(ofSize:)))!
        method_exchangeImplementations(m5, m6)
        
        let m7 = class_getClassMethod(UIFont.self, #selector(italicSystemFont(ofSize:)))!
        let m8 = class_getClassMethod(UIFont.self, #selector(italicCustomFont(ofSize:)))!
        method_exchangeImplementations(m7, m8)
        
        let m9 = class_getClassMethod(UIFont.self, #selector(systemFont(ofSize:weight:)))!
        let m10 = class_getClassMethod(UIFont.self, #selector(customFont(ofSize:weight:)))!
        method_exchangeImplementations(m9, m10)
        
        let m11 = class_getClassMethod(UIFont.self, #selector(preferredFont(forTextStyle:compatibleWith:)))!
        let m12 = class_getClassMethod(UIFont.self, #selector(customFont(forTextStyle:compatibleWith:)))!
        method_exchangeImplementations(m11, m12)
        
        // 新增:Swizzle 实例初始化方法
        let initMethod = class_getInstanceMethod(UIFont.self, #selector(UIFont.init(descriptor:size:)))!
        let customInitMethod = class_getInstanceMethod(UIFont.self, #selector(UIFont.customInit(descriptor:size:)))!
        method_exchangeImplementations(initMethod, customInitMethod)
    }()
}

private extension UIFont {
    // 改造原有自定义字体方法,加入语言判断和安全降级
    @objc class func customFont(ofSize fontSize: CGFloat) -> UIFont {
        let targetFont = preferredFontNameForCurrentLanguage()
        return UIFont(name: targetFont, size: fontSize) ?? UIFont.systemFont(ofSize: fontSize)
    }
    
    @objc class func customFont(forTextStyle style: UIFont.TextStyle) -> UIFont {
        let fontSize = UIFont.preferredFont(forTextStyle: style).pointSize
        let targetFont = preferredFontNameForCurrentLanguage()
        return UIFont(name: targetFont, size: fontSize) ?? UIFont.preferredFont(forTextStyle: style)
    }
    
    @objc class func boldCustomFont(ofSize fontSize: CGFloat) -> UIFont {
        let targetFont = "\(preferredFontNameForCurrentLanguage())-Bold"
        return UIFont(name: targetFont, size: fontSize) ?? UIFont.boldSystemFont(ofSize: fontSize)
    }
    
    @objc class func italicCustomFont(ofSize fontSize: CGFloat) -> UIFont {
        let targetFont = "\(preferredFontNameForCurrentLanguage())-Italic"
        return UIFont(name: targetFont, size: fontSize) ?? UIFont.italicSystemFont(ofSize: fontSize)
    }
    
    @objc class func customFont(ofSize fontSize: CGFloat, weight: UIFont.Weight) -> UIFont {
        var targetFont = preferredFontNameForCurrentLanguage()
        switch weight {
        case .bold: targetFont += "-Bold"
        case .italic: targetFont += "-Italic"
        default: break
        }
        return UIFont(name: targetFont, size: fontSize) ?? UIFont.systemFont(ofSize: fontSize, weight: weight)
    }
    
    @objc class func customFont(forTextStyle style: UIFont.TextStyle, compatibleWith traitCollection: UITraitCollection?) -> UIFont {
        let fontSize = UIFont.preferredFont(forTextStyle: style, compatibleWith: traitCollection).pointSize
        let targetFont = preferredFontNameForCurrentLanguage()
        return UIFont(name: targetFont, size: fontSize) ?? UIFont.preferredFont(forTextStyle: style, compatibleWith: traitCollection)
    }
    
    // 新增:拦截初始化方法,替换系统字体描述符
    @objc convenience init(customInit descriptor: UIFontDescriptor, size: CGFloat) {
        // 先调用原初始化方法(Swizzle后,这里的customInit指向原init方法)
        self.init(customInit: descriptor, size: size)
        
        // 只替换系统字体的描述符
        guard descriptor.fontAttributes[.family] as? String == UIFont.systemFontFamilyName else {
            return
        }
        
        // 根据当前语言获取目标字体
        let targetFont = preferredFontNameForCurrentLanguage()
        var newAttributes = descriptor.fontAttributes
        newAttributes[.family] = targetFont
        
        // 匹配原字体的字重
        if let traits = newAttributes[.traits] as? [UIFontDescriptor.TraitKey: Any],
           let weightValue = traits[.weight] as? CGFloat {
            switch weightValue {
            case UIFont.Weight.bold.rawValue:
                newAttributes[.family] = "\(targetFont)-Bold"
            case UIFont.Weight.italic.rawValue:
                newAttributes[.family] = "\(targetFont)-Italic"
            default: break
            }
        }
        
        // 替换为自定义字体描述符
        let customDescriptor = UIFontDescriptor(fontAttributes: newAttributes)
        if let finalFont = UIFont(descriptor: customDescriptor, size: size) {
            self = finalFont
        }
    }
    
    // 新增:根据当前语言返回对应字体名
    private static func preferredFontNameForCurrentLanguage() -> String {
        let languageCode = Locale.current.languageCode ?? "en"
        switch languageCode {
        case "zh", "zh-Hans", "zh-Hant":
            return "PingFangSC-Regular" // 中文用苹方
        case "ja":
            return "HiraginoSans-W3" // 日文用冬青黑体
        default:
            return "Chalkduster" // 其他语言用测试字体
        }
    }
}

2. 关键注意事项

  • 安全降级:所有自定义字体创建都加了??降级到系统字体,避免因字体未导入、名称拼写错误导致崩溃(你原来的!强制解包在生产环境很危险)。
  • 字重匹配:针对粗体、斜体做了对应处理,确保系统字体的样式能映射到自定义字体的对应字重版本(需要你的自定义字体包含对应字重文件)。
  • 动态语言切换:如果支持App内切换语言,需要在语言切换后重新调用UIFont.setSystemFont,并发送通知让所有控件刷新字体。

3. 验证效果

修改后重新运行App,Xib/Storyboard里的系统字体就会和代码创建的视图一样,自动切换为对应语言的自定义字体了。

内容的提问来源于stack exchange,提问作者user11497206

火山引擎 最新活动