一、期望
手动设置系统字体大小,App里面的字体能跟随发生变化,并有最大最小字体限制。
二、方案
1、App内生效和重启App生效
1. 需要重启app才能生效(快手)
借助runtime,在app启动的时候交换方法实现。
特点:需要重启app才能生效。无侵入,只需要添加一个UIFont分类,并在appdelegate里面调用交换方法。对整体项目改动小。
2. app内实时刷新(微信、QQ)
① App内设置字体大小,选择字体后,发送通知。切回到你需要改变的界面上时,在收到通知的方法里改变当前界面字体。(微信)
② 调整系统字体大小,切换到App内,字体大小自动更改,用DynamicType实现(QQ)
特点:app内实时刷新。可以整体替换,第三方重新pod,但项目整体改动较多
③ 保存字体大小到本地,所有用到字体的地方实时获取或刷新
采用:app内实时刷新
2、全局替换和部分替换
2.1 整体全部手动替换,搜索所有的systemFont,改为xx_systemFont,第三方重新pod,但项目整体改动较多,有风险
2.2 几个核心页面做下适配,其他不管
采用:6个核心页面做下适配
三、代码实现
1、重启app生效(快手)
添加一个UIFont分类,更改systemFont类方法的实现
UIFont.swizzleSystemFont()
extension UIFont {
class func swizzleSystemFont() {
let cls: AnyClass? = object_getClass(self)
if let systemMethod = class_getInstanceMethod(cls, #selector(UIFont.systemFont(ofSize:))), let swizzMethod = class_getInstanceMethod(cls, #selector(UIFont.my_systemFont(ofSize:))) {
method_exchangeImplementations(systemMethod, swizzMethod);
}
...
}
// 最大字号18,最小字号12
class func my_finalFontSize(ofSize: CGFloat) -> CGFloat {
// 计算出 body 类型比默认的大小要变化了多少,然后在 pointSize 的基础上叠加这个变化
let font = UIFont.preferredFont(forTextStyle: TextStyle.body)
let offsetPointSize = font.pointSize - 17 // default UIFontTextStyleBody fontSize is 17
var finalPointSize = ofSize + offsetPointSize
finalPointSize = max(min(finalPointSize, 18), 12)
return finalPointSize
}
@objc class func my_systemFont(ofSize: CGFloat) -> UIFont {
let finalPointSize = my_finalFontSize(ofSize: ofSize)
return my_systemFont(ofSize: finalPointSize)
}
// 不用runtime专用方法
class func xx_systemFont(ofSize: CGFloat) -> UIFont {
let finalPointSize = my_finalFontSize(ofSize: ofSize)
return UIFont.systemFont(ofSize: finalPointSize)
}
2、App内实时刷新
1.注册一个字体改变的通知。选择字体后,发送通知。切回到你需要改变的界面上时,在收到通知的方法里改变当前界面字体。(微信)
2.用DynamicType实现(QQ)
效果:调整系统字体大小,切换到App内,字体大小自动更改
缺点:字体大小只能是给定的10种类型之一
// 收到系统字体大小改变的通知刷新对应的UI字体
NotificationCenter.default.addObserver(self, selector: #selector(fontChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
@objc func fontChange() {
let font = UIFont.preferredFont(forTextStyle: .body)
testLab.font = UIFont.systemFont(ofSize: font.pointSize)
// 这里调用setNeedsLayout做一个需要重新布局的标记,在下一个draw周期(60Hz)自动重绘
}
// iOS10之后,系统字体改变不需要接受通知,设置adjustsFontForContentSizeCategory即可自动变化
testLab.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
testLab.adjustsFontForContentSizeCategory = true
// 必要的时候更新frame
3.保存字体大小到本地,所有用到字体的地方实时获取或刷新
3、最终方案:
全局替换系统字体方法,会有个问题:很多地方view大小没做适配。采用适配4、5个核心页面的方案。
实现方法:给UIFont分类添加新的方法,需要适配的地方把systemFont改为xx_systemFont即可
特点:哪里需要适配就改哪里,灵活度和自由度自己控制
extension UIFont {
class func xx_systemFont(ofSize: CGFloat) -> UIFont {
let finalPointSize = my_finalFontSize(ofSize: ofSize)
return UIFont.systemFont(ofSize: finalPointSize)
}
}
label.font = UIFont.xx_systemFont(ofSize: 16)
四、遇到问题
1、Method 'initialize()' defines Objective C class method 'initialize', which is not permitted by Swift
Swift4开始废弃load方法和initital方法了,写个类方法交换实现,然后直接在appdelegate里面调用类方法即可。UIButton可行,但是UIFont不可行(UIFont是类方法,写错了就不生效)。
2、UIFont的systemFont设置了没生效,因为systemFont是类方法,写错了
3、百度到的一些方案遇到些问题UILabel的font方法获取不到,willMove(toSuperview:)会出现UIButtonLabel的情况,属性字符串的兼容情况等。
网友评论