在开发落格输入法 macOS 的时候,我遇到了一个比较奇葩的问题,这个问题一直困扰我到现在——当有些地方需要垂直居中显示一排文字的时候,如何让这些字真正的“居中”?
乍看之下这似乎没什么道理,垂直居中嘛……等等,macOS 上的 NSTextField 还真没有办法让你的一行文字垂直居中……?♂️
第一代方案
后来,我参考网上的一些解决方案,自己动手写了一个 NSTextFieldCell 的子类,这样文本就能真正地实现垂直居中了,代码如下:
Swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
final class VerticallyCenteredTextFieldCell: NSTextFieldCell {
func adjustedFrame(toVerticallyCenterText rect: NSRect) -> NSRect {
// super would normally draw text at the top of the cell
var titleRect = super.titleRect(forBounds: rect)
let minimumHeight = self.cellSize(forBounds: rect).height
titleRect.origin.y += (titleRect.height – minimumHeight) / 2
titleRect.size.height = minimumHeight
return titleRect
}
override func edit(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, event: NSEvent?) {
super.edit(withFrame: adjustedFrame(toVerticallyCenterText: rect), in: controlView, editor: textObj, delegate: delegate, event: event)
}
override func select(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, start selStart: Int, length selLength: Int) {
super.select(withFrame: adjustedFrame(toVerticallyCenterText: rect), in: controlView, editor: textObj, delegate: delegate, start: selStart, length: selLength)
}
override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) {
super.drawInterior(withFrame: adjustedFrame(toVerticallyCenterText: cellFrame), in: controlView)
}
override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
super.draw(withFrame: cellFrame, in: controlView)
}
}
|
第二代方案
后来为了提升布局效率,我不再使用 NSTextField ,而是直接在 NSView 上绘制文本,不过原理还是一样的,根据字体视觉居中来设置 y 值偏移量。
但是好景不长,很快,落格输入法就要支持候选栏自定义任意字体了,这下就很糟糕了——因为并不是所有的字体都有着同样的高度,比如同是 18 号大小的中文字,系统字体和系统自带娃娃体字体高度就是不同的:

你看,同样大小的字号,右侧娃娃体明显比左侧系统自带字体要矮一些——关键是变矮后,他们都是基于字体基准线来排布的,导致娃娃体这个字体在实际视觉中“靠下”了。
事实上类似娃娃体这样的字体有很多,比如宋体、楷体等中文字体几乎全都是不尽相同的低矮设计,这样你就很难找到一个通用的偏移量去垂直居中文字。
第三代方案
这次,既然已经直接绘制文本了,那么我就从字体属性本身下手,查看字体声明,我发现了一个有趣的属性 boundingRectForFont ,这个属性返回一个表示文字字符边界的矩形,这就很有意思了,不同的字,字符的高度不同,边界的高度也就不一样。
所以我在每次布局时都创建一个相同字号的系统字体,获取字体边界,再获取用户自定义的字体的边界,两者取高度差,就是用户字体和系统自带字体的字符高度差了。然后,再根据上文中的思路,调整对应偏移量,即可得到真正的视觉垂直居中布局:

另,有些老字体比如 宋体 就很尴尬,它在同样大小字号下比系统默认字体的矩形还要大,但实际字体大小与系统字体相同,同时字体整体是“底部对齐”,于是差值是负数,结果导致这个字体本身就靠下了,偏移后反而更靠下了……对于这类,我大力出奇迹,直接取绝对值就好了……?♂️
转载请注明:逗比根据地 » 让 iOS macOS 中文字体实现视觉垂直居中