關於 iOS 中多行文字行間距這個問題蛋疼了幾年了,回憶一下整個歷程:
一開始,UI 同學使用 PhotoShop 實現 UI 稿,PhotoShop 的 Label 在相同字型下的高度與 iOS 比就不準,並且使用標註工具進行文字標註時總是緊貼著字形的上下邊進行標註,而字型本身有 LineHeight,字形上下是有間距的。為了達到 UI 稿效果,只能用模擬器對著相同尺寸 UI 稿,用標尺工具一點點比較,試出間距值,標註值僅供參考。
後來 UI 同學換成了用 Sketch 實現 UI 稿,由於 Sketch 使用和 iOS 相同的文字渲染技術,在 Sketch 上新建一個 Label,文字帶 LineHeight,有間距,單行文字或文字與其他元素之間的間距終於準確了。
但是 Sketch 中處理多行文字時只有 LineHeight 的概念,沒有 UILabel 中 LineSpacing 的概念,LineSpacing 只會在行與行中間新增間距,每一行的 LineHeight 保持不變,導致 UI 稿中多行文字修改 LineHeight 之後,用 LineSpacing 並不能完美匹配 UI 稿效果,而且 LineHeight 的變化也會導致文字在和其他控制元件對齊時與標註對不上。NSParagraphyStyle 雖然有 maximumLineHeight 和 minimumLineHeight 屬性,但設定以後是在文字頂部多出間距,而不是上下均勻間距。為了解決這個問題,參考過 iOS 文字對齊,如何畫素般精確還原設計稿,使用 Sketch 外掛將 LineHeight 修正成 LineSpacing 的效果,但 UI 同學反饋外掛不能用,我也沒仔細研究如何定製 Sketch 外掛,另外,每次用外掛修正也比較麻煩,UI 同學存在遺漏的可能性。
另外,iOS 的 LineSpacing 一直有個 Bug,一旦中文設定了 LineSpacing,在單行情況下底部會多出 LineSpacing 的間距,多行時就沒有這個問題,英文單行也沒有這個問題。為了解決這個問題,會判斷文字是否超過了一行,如果不超過一行就不設定 LineSpacing。後來嫌麻煩,直接用 baseline 對齊而不是 bottom 對齊,offset 需要加上字型 descent 的大小。
今天偶然看到了 在iOS中如何正確的實現行間距與行高 - 掘金 這篇文章,豁然開朗。雖然設定 maximumLineHeight 和 minimumLineHeight 會導致顯示有偏移,但整體高度是對的,利用 baselineOffset 將偏移修復即可,修復公式為 (lineHeight - label.font.lineHeight) / 4
。
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.maximumLineHeight = lineHeight;
paragraphStyle.minimumLineHeight = lineHeight;
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
attributes[NSParagraphStyleAttributeName] = paragraphStyle;
CGFloat baselineOffset = (lineHeight - font.lineHeight) / 4;
attributes[NSBaselineOffsetAttributeName] = @(baselineOffset);
複製程式碼
經過同 Sketch 對比,與 UI 效果一致。由於設定的是 LineHeight,中文單行文字也沒有了底部多出間隔的問題了。最後將相關程式碼抽成一個 Utils,以後如果 UI 修改了文字的 LineHeight,直接使用這個 Utils 配置 NSAttributedString,就能完美適配 UI 的效果和標註,神清氣爽!
一些注意事項:
- 每種字型的 LineHeight 是不同的,例如 SFUI 的 LineHeight 是字號的 1.2 倍,PingFangSC 的 LineHeight 是字號的 1.4 倍。
- SFUI 中沒有中文字型,最後系統會 fall back 到 PingFangSC,字形的顯示是相同的,但是由於字型不用,導致 LineHeight 不一樣。用
systemFontOfSize:size
和fontWithName:@"PingFangSC-Regular" size:size
設定 UILabel 的 font,相同中文內容的 UILabel 高度不一樣。 - baselineOffset 很奇怪,移動的效果是設定值的兩倍,例如設定 1 pt,向上移動 2 pt,所以修復公式最後是 / 4 而不是 / 2。
Article by 傳人 Joe