
一. 引言
在产品或设计同学的走查过程中,经常会提出一个问题:在宽屏 iPhone 上,字体看起来好像变小了,需要进行适配。可能你会有点摸不着头脑------明明代码里写的都是 16pt,字体怎么会变小呢?
他们可能会说是屏幕分辨率高、屏幕宽度大之类的原因。先说结论:字体没有变小。在 iOS 里,字体的大小一直是 16pt,不会因为分辨率更高而变小。所谓"变小",其实是一种视觉错觉。
原因在于:随着屏幕宽度增加,内容布局相对变得更松散,人眼的感知就会觉得字体偏小。而 Retina 屏幕只是用更多像素去显示同样大小的文字,让字体更清晰,而不是更小。
不过他们的诉求应该还是确定的,就是希望把字体在宽手机上放大一点,所以我们还是要做一些调整。
二. iOS 字体渲染原理
在讲解解决方案之前呢,我们先看看 iOS 字体是如何渲染的。核心概念有两个:逻辑尺寸(pt)和物理像素(px)。
2.1 pt 与 px 的区别
- pt(point,点):iOS 中的逻辑尺寸单位。无论设备屏幕多大,16pt 的文字在逻辑上都是相同大小。
- px(像素):屏幕的物理像素数量。Retina 屏幕用更多的像素去描绘同样大小的逻辑尺寸,从而让文字更清晰。
举个例子:
| 机型 | 屏幕宽度(pt) | 屏幕像素(px) | 字体 16pt 对应像素 |
|---|---|---|---|
| iPhone 8 | 375pt | 750px | 32px(2x) |
| iPhone 15 Pro | 393pt | 1179px | 48px(3x) |
可以看到,逻辑尺寸(16pt)没有改变,只是物理像素更多了,字体更清晰而已。
2.2 为什么宽屏手机上字体"看起来小"?
即便逻辑尺寸没变,人的视觉仍然会有错觉,主要原因有两个:
1.屏幕宽度增加,布局变松散
- 当屏幕宽度 > 375pt 时,原本紧凑的文字和控件之间的空间变大,文字在视觉上占比下降。
- 结果就是"看起来比以前小"。
2.视觉感知规律
- 人眼对大面积空间中的相对大小更敏感,小文字在宽屏上容易被周围空白"吞掉"。
所以,字体本身一直是 16pt,只是视觉上产生了"偏小"的错觉。
iOS 字体大小由 pt 决定,不会因为分辨率高而变小,Retina 屏幕只是用更多像素描绘文字,让字体更清晰。
三. 如何全局修改字体大小?
在实际项目中,我们经常会遇到这样的问题:在宽屏 iPhone 上,原本写好的 16pt 字体看起来好像变小了。为了保证视觉一致性,我们需要在全项目范围内统一调整字体大小。不过,不同技术栈的项目实现方式有所不同,这与项目的前期建设密切相关。
下面我们从多种技术栈出发,讲解几种全局修改字体的方法。
3.1 Hook 字体方法(不推荐,但全局生效最快)
Hook 方法是通过 runtime 替换系统字体方法,让所有控件默认使用缩放后的字体。这样做的优点是:
- 无论是 Objective-C、Swift,甚至第三方控件都可以生效
- 不需要手动替换每个控件的字体
但是,这种方式也有明显缺点:
- 系统内部控件字体也会被改,可能引发布局或显示问题
- 第三方库控件可能出现意料之外的效果
- 调试难度大,风险较高
代码实现如下:
objectivec
#import <UIKit/UIKit.h>
@interface UIFont (Hook)
@end
objectivec
#import "UIFont+Hook.h"
#import <objc/runtime.h>
#define kFontScale ([UIScreen mainScreen].bounds.size.width > 375.0 ? 1.048 : 1.0)
@implementation UIFont (Hook)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// systemFontOfSize:
Method originalSystem = class_getClassMethod(self, @selector(systemFontOfSize:));
Method swizzledSystem = class_getClassMethod(self, @selector(ph_systemFontOfSize:));
method_exchangeImplementations(originalSystem, swizzledSystem);
// boldSystemFontOfSize:
Method originalBold = class_getClassMethod(self, @selector(boldSystemFontOfSize:));
Method swizzledBold = class_getClassMethod(self, @selector(ph_boldSystemFontOfSize:));
method_exchangeImplementations(originalBold, swizzledBold);
// fontWithName:size:
Method originalName = class_getClassMethod(self, @selector(fontWithName:size:));
Method swizzledName = class_getClassMethod(self, @selector(ph_fontWithName:size:));
method_exchangeImplementations(originalName, swizzledName);
});
}
+ (UIFont *)ph_systemFontOfSize:(CGFloat)fontSize {
return [self ph_systemFontOfSize:(fontSize * kFontScale)];
}
+ (UIFont *)ph_boldSystemFontOfSize:(CGFloat)fontSize {
return [self ph_boldSystemFontOfSize:(fontSize * kFontScale)];
}
+ (UIFont *)ph_fontWithName:(NSString *)fontName size:(CGFloat)fontSize {
return [self ph_fontWithName:fontName size:(fontSize * kFontScale)];
}
@end
3.2 Objective-C 项目常用方法:宏定义
在纯 OC 项目中,最常见、可控的做法是使用宏定义。例如我们项目中定义了如下宏:
objectivec
#define kFontScale ([UIScreen mainScreen].bounds.size.width > 375.0 ? 1.048 : 1.0)
#define kSystemFont(x) [UIFont systemFontOfSize:(x * kFontScale)]
#define kBoldFont(x) [UIFont boldSystemFontOfSize:(x * kFontScale)]
#define kNumberFont(x) [UIFont bebasFontOfSize:(x * kFontScale)]
使用方式非常简单:
objectivec
label.font = kSystemFont(16);
button.titleLabel.font = kBoldFont(18);
这种方式的优点是:
- 简单明了,统一管理
- 易于维护和修改比例
缺点是:
- 需要在项目中统一替换原来的 systemFontOfSize 调用
总的来说,这是 OC 项目中最稳妥的全局字体适配方案。
3.3 Swift 项目统一方法
在 Swift 项目中,可以封装一个统一的字体 Helper,或者分了,例如我们的 ZMFontHelper:
Swift
/// 设置字体
/// - Parameters:
/// - fontType: 字体类型
/// - fontSize: 字体大小
/// - fontWeight: 字体粗细
/// - Returns: UIFont
public class func font(fontType: ZMFontType = .system,
fontSize: CGFloat,
fontWeight: UIFont.Weight = .regular) -> UIFont {
let scale: CGFloat = UIScreen.main.bounds.size.width > 375 ? 1.048 : 1.0
let finalSize = fontSize * scale
switch fontType {
case .system:
return UIFont.systemFont(ofSize: finalSize, weight: fontWeight)
}
}
使用方式:
Swift
label.font = ZMFontHelper.font(fontSize: 16)
button.titleLabel?.font = ZMFontHelper.font(fontSize: 18, fontWeight: .bold)
优点:
- 面向对象,易于扩展
- 可以兼顾 OC + Swift 混合项目(通过桥接)
- 易于统一调整字体比例
3.4 SwiftUI 项目
在 SwiftUI 中,推荐使用自定义 ViewModifier 来实现全局字体适配:
Swift
struct ScaledFont: ViewModifier {
var size: CGFloat
var scaledSize: CGFloat {
UIScreen.main.bounds.width > 375 ? size * 1.048 : size
}
func body(content: Content) -> some View {
content.font(.system(size: scaledSize))
}
}
extension View {
func scaledFont(_ size: CGFloat) -> some View {
self.modifier(ScaledFont(size: size))
}
}
使用方式:
Swift
Text("Hello World")
.scaledFont(16)
优点:
- 全局统一,代码优雅
- 易于维护和扩展
- 完美契合 SwiftUI 生态
3.5 项目中每个地方都单独写字体
如果项目中每个控件都是单独设置字体(没有统一规范),目前只能通过搜索替换的方式逐步改成统一方法或宏/Helper。
- 建议在后续项目中尽量使用统一方法
- 避免重复手动修改,提高可维护性
四. 结语
在 iOS 项目中,字体大小并不会因为屏幕分辨率高而变小。所谓在宽屏手机上字体"变小",只是视觉错觉:屏幕宽度增加、布局相对变得更松散,导致人眼感知上觉得字体偏小。
理解这一点之后,我们就可以科学地在全项目范围内统一调整字体大小。无论是使用宏定义、Swift Helper、SwiftUI Modifier,还是在特殊情况下采用 Hook 方法,每种方案都有其适用场景。关键在于选择与项目技术栈和维护需求相匹配的方案,从而既满足设计师在大屏设备上的视觉需求,又保证项目的可维护性和一致性。
最终,全局字体适配不是简单地放大字号,而是基于 iOS 渲染原理和视觉平衡的合理调整,让用户在不同机型上都能获得最佳阅读体验。