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

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

關于行間距 lineSpacing

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

26-A

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

這種時候,設計師就會提出行間距的需求,希望讓文本展示得更美觀。類似的標注就會像這樣:

26-B

通常來說既然設計師要求的是行間距,那么我們直接設置 lineSpacing 就好。但是 UILabel 是沒有這么一個直接暴露的屬性的,想要修改 lineSpacing,我們需要借助 NSAttributedString 來實現,示意代碼:

運行一下觀察效果:

26-C

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

26-D

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

正確的實現行間距

先看示意圖:

26-E

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

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

知道了原因后問題就好解決了,我們需要在設置 lineSpacing 時,減去這個系統的自帶邊距:

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

26-F

關于行高 lineHeight

如果你只關心 iOS 設備上的文本展示效果,那么看到這里就已經夠了。但是我需要的是 iOS 和 Android 展現出一模一樣的效果,所以光有行間距是不能滿足需求的。主要的原因在前言也提到了,Android 設備上的文字上下默認留白(上一節圖中藍色和紅色重疊的部分)和 iOS 設備上的是不一致的:

26-G

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

這時候我們可以通過設置 lineHeight 來使得每一行文本的高度一致,lineHeight 設置為 30pt 的情況下,一行文本高度一定是 30pt,兩行文本高度一定是 60pt。雖然文字的渲染上會有細微的差別,但是布局上的差別將被完全的抹除。lineHeight 同樣可以借助 NSAttributedString 來實現,示意代碼:

運行一下觀察效果:

27-A

在 debug 模式下確認了下文本的高度的確正確的,但是為什么文字都顯示在了行底呢?

修正行高增加后文字的位置

修正文字在行中展示的位置,我們可以用 baselineOffset 屬性來搞定。這個屬性十分有用,在實現上標下標之類的需求時也經常用到它。經過調試,發現最合適的值是 (lineHeight - label.font.lineHeight) / 4(尚未搞清楚為什么是除以 4 而不是除以 2,希望知道的老司機指點一二)。最終的代碼示例如下:

貼一下在不同字號和行高下的展示效果:

27-B

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

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

27-C

著色的區域都是文本的繪制區域,其中看上去是橙色的區域是 lineSpacing,綠色的區域是 lineHeight。但是為什么單行的文本系統也要展示一個 lineSpacing 啊!?坑爹呢這是!?

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

總結:

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

補充:

NSFontAttributeName                設置字體屬性,默認值:字體:Helvetica(Neue) 字號:12
NSForegroundColorAttributeNam      設置字體顏色,取值為 UIColor對象,默認值為黑色
NSBackgroundColorAttributeName     設置字體所在區域背景顏色,取值為 UIColor對象,默認值為nil, 透明色
NSLigatureAttributeName            設置連體屬性,取值為NSNumber 對象(整數),0 表示沒有連體字符,1 表示使用默認的連體字符
NSKernAttributeName                設定字符間距,取值為 NSNumber 對象(整數),正值間距加寬,負值間距變窄
NSStrikethroughStyleAttributeName  設置刪除線,取值為 NSNumber 對象(整數)
NSStrikethroughColorAttributeName  設置刪除線顏色,取值為 UIColor 對象,默認值為黑色
NSUnderlineStyleAttributeName      設置下劃線,取值為 NSNumber 對象(整數),枚舉常量 NSUnderlineStyle中的值,與刪除線類似
NSUnderlineColorAttributeName      設置下劃線顏色,取值為 UIColor 對象,默認值為黑色
NSStrokeWidthAttributeName         設置筆畫寬度,取值為 NSNumber 對象(整數),負值填充效果,正值中空效果
NSStrokeColorAttributeName         填充部分顏色,不是字體顏色,取值為 UIColor 對象
NSShadowAttributeName              設置陰影屬性,取值為 NSShadow 對象
NSTextEffectAttributeName          設置文本特殊效果,取值為 NSString 對象,目前只有圖版印刷效果可用:
NSBaselineOffsetAttributeName      設置基線偏移值,取值為 NSNumber (float),正值上偏,負值下偏
NSObliquenessAttributeName         設置字形傾斜度,取值為 NSNumber (float),正值右傾,負值左傾
NSExpansionAttributeName           設置文本橫向拉伸屬性,取值為 NSNumber (float),正值橫向拉伸文本,負值橫向壓縮文本
NSWritingDirectionAttributeName    設置文字書寫方向,從左向右書寫或者從右向左書寫
NSVerticalGlyphFormAttributeName   設置文字排版方向,取值為 NSNumber 對象(整數),0 表示橫排文本,1 表示豎排文本
NSLinkAttributeName                設置鏈接屬性,點擊后調用瀏覽器打開指定URL地址
NSAttachmentAttributeName          設置文本附件,取值為NSTextAttachment對象,常用于文字圖片混排
NSParagraphStyleAttributeName      設置文本段落排版格式,取值為 NSParagraphStyle 對象
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容