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




