在iOS中如何正確的實現行間距與行高

最近準備給 VirtualView-iOS 的文本元素新增一個 lineHeight 屬性,以便和 VirtualView-Android 配合時能更精確的保證雙平臺的一致性。面向 Google 以及 Stack Overflow 編程了一會後發現,能查到的資料大部分是介紹如何實現 lineSpacing 屬性,而不是 lineHeight。可是我就是由於 iOS 和 Android 的默認 lineSpacing 不一致因此纔想實現個 lineHeight 啊!仍是須要本身動手豐衣足食,順帶整理成文章造福後人。git

關於行間距 lineSpacing

先貼出一張 iOS 中 UILabel 的默認排版樣式:github

26-A

你們也都能看出來,默認的排版樣式中,文本的行間距很小,顯得文本十分擠。編程

這種時候,設計師就會提出行間距的需求,但願讓文本展現得更美觀。相似的標註就會像這樣:bash

26-B

一般來講既然設計師要求的是行間距,那麼咱們直接設置 lineSpacing 就好。可是 UILabel 是沒有這麼一個直接暴露的屬性的,想要修改 lineSpacing,咱們須要藉助 NSAttributedString 來實現,示意代碼:app

NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineSpacing = 10;
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
[attributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
label.attributedText = [[NSAttributedString alloc] initWithString:label.text attributes:attributes];
複製代碼

運行一下觀察效果:佈局

26-C

雖然用咱們的眼睛看上去好像沒什麼問題,可是設計師的火眼金睛一下就能看出來,和設計稿要求的有差距:字體

26-D

怎麼會成這樣!?這跟說好的不同對不對!?不要慌,我來細細解釋下。ui

正確的實現行間距

先看示意圖:spa

26-E

紅色區域是默認繪製單行文本會佔用的區域,能夠看到文字的上下是有一些留白的(藍色和紅色重疊的部分)。設計師是想要藍色區域高度爲 10pt,而咱們直接設置 lineSpacing 會將兩行紅色區域中間的綠色區域高度設置爲 10pt,這就是問題的根源了。debug

那麼這個紅色的區域高度是多少呢?答案是 label.font.lineHeight,它是使用指定字體繪製單行文本的原始行高。

知道了緣由後問題就好解決了,咱們須要在設置 lineSpacing 時,減去這個系統的自帶邊距:

NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineSpacing = 10 - (label.font.lineHeight - label.font.pointSize);
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
[attributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
label.attributedText = [[NSAttributedString alloc] initWithString:label.text attributes:attributes];
複製代碼

觀察一下效果,完美契合:

26-F

關於行高 lineHeight

若是你只關心 iOS 設備上的文本展現效果,那麼看到這裏就已經夠了。可是我須要的是 iOS 和 Android 展示出如出一轍的效果,因此光有行間距是不能知足需求的。主要的緣由在前言也提到了,Android 設備上的文字上下默認留白(上一節圖中藍色和紅色重疊的部分)和 iOS 設備上的是不一致的:

26-G

左側是 iOS 設備,右側 Android 設備,能夠看到一樣是顯示 20 號的字體,安卓的行高會偏高一些。在不一樣的 Android 設備上使用的字體不同,可能還會出現更多的差異。若是不想辦法抹平這差異,就不能真正意義上實現雙端一致了。

這時候咱們能夠經過設置 lineHeight 來使得每一行文本的高度一致,lineHeight 設置爲 30pt 的狀況下,一行文本高度必定是 30pt,兩行文本高度必定是 60pt。雖然文字的渲染上會有細微的差異,可是佈局上的差異將被徹底的抹除。lineHeight 一樣能夠藉助 NSAttributedString 來實現,示意代碼:

NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.maximumLineHeight = lineHeight;
paragraphStyle.minimumLineHeight = lineHeight;
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
[attributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
label.attributedText = [[NSAttributedString alloc] initWithString:label.text attributes:attributes];
複製代碼

運行一下觀察效果:

27-A

在 debug 模式下確認了下文本的高度的確正確的,可是爲何文字都顯示在了行底呢?

修正行高增長後文字的位置

修正文字在行中展現的位置,咱們能夠用 baselineOffset 屬性來搞定。這個屬性十分有用,在實現上標下標之類的需求時也常常用到它。通過調試,發現最合適的值是 (lineHeight - label.font.lineHeight) / 4(還沒有搞清楚爲何是除以 4 而不是除以 2,但願知道的老司機指點一二)。最終的代碼示例以下:

NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.maximumLineHeight = lineHeight;
paragraphStyle.minimumLineHeight = lineHeight;
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
[attributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
CGFloat baselineOffset = (lineHeight - label.font.lineHeight) / 4;
[attributes setObject:@(baselineOffset) forKey:NSBaselineOffsetAttributeName];
label.attributedText = [[NSAttributedString alloc] initWithString:label.text attributes:attributes];
複製代碼

貼一下在不一樣字號和行高下的展現效果:

27-B

行高和行間距同時使用時的一個問題

不得不說行高和行間距咱們都已經能夠完美的實現了,可是我在嘗試同時使用它們時,發現了 iOS 的一個 bug(固然也多是一個 feature,畢竟不 crash 都不必定是 bug):

27-C

着色的區域都是文本的繪製區域,其中看上去是橙色的區域是 lineSpacing,綠色的區域是 lineHeight。可是爲何單行的文本系統也要展現一個 lineSpacing 啊!?坑爹呢這是!?

好在咱們一般是行高和行間距針對不一樣的需求分別獨立使用的,它們在分開使用時不會觸發這個問題。因此在 VirtualView-iOS 庫中,我暫且將高度計算的邏輯保持和系統一致了。

總結

至此,成功的爲 VirtualView-iOS 添加了對 lineHeight 屬性的支持,更多的實現細節你們能夠到開源庫中直接看源代碼。但願咱們的 Tangram 方案能夠更加完善,幫助更多的人一次開發兩端同時使用,用一塊七巧板拼出大千世界。

相關文章
相關標籤/搜索