如何正確的使用NSAttributedString在iOS中實(shí)現(xiàn)行間距與行高

最近準(zhǔn)備給 VirtualView-iOS 的文本元素新增一個(gè) lineHeight 屬性协怒,以便和 VirtualView-Android 配合時(shí)能更精確的保證雙平臺(tái)的一致性揪惦。面向 Google 以及 Stack Overflow 編程了一會(huì)后發(fā)現(xiàn),能查到的資料大部分是介紹如何實(shí)現(xiàn) lineSpacing 屬性疗认,而不是 lineHeight。但是我就是因?yàn)?iOS 和 Android 的默認(rèn) lineSpacing 不一致所以才想實(shí)現(xiàn)個(gè) lineHeight 胺啤横漏!還是需要自己動(dòng)手豐衣足食,順帶整理成文章造福后人熟掂。

關(guān)于行間距 lineSpacing

先貼出一張 iOS 中 UILabel 的默認(rèn)排版樣式:

26-A

大家也都能看出來缎浇,默認(rèn)的排版樣式中,文本的行間距很小赴肚,顯得文本十分?jǐn)D素跺。

這種時(shí)候,設(shè)計(jì)師就會(huì)提出行間距的需求誉券,希望讓文本展示得更美觀指厌。類似的標(biāo)注就會(huì)像這樣:

26-B

通常來說既然設(shè)計(jì)師要求的是行間距,那么我們直接設(shè)置 lineSpacing 就好踊跟。但是 UILabel 是沒有這么一個(gè)直接暴露的屬性的踩验,想要修改 lineSpacing,我們需要借助 NSAttributedString 來實(shí)現(xiàn),示意代碼:

運(yùn)行一下觀察效果:

26-C

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

26-D

怎么會(huì)成這樣!袭异?這跟說好的不一樣對(duì)不對(duì)D屏?不要慌扁远,我來細(xì)細(xì)解釋下俊鱼。

正確的實(shí)現(xiàn)行間距

先看示意圖:

26-E

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

那么這個(gè)紅色的區(qū)域高度是多少呢帝火?答案是 label.font.lineHeight,它是使用指定字體繪制單行文本的原始行高湃缎。

知道了原因后問題就好解決了犀填,我們需要在設(shè)置 lineSpacing 時(shí),減去這個(gè)系統(tǒng)的自帶邊距:

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

26-F

關(guān)于行高 lineHeight

如果你只關(guān)心 iOS 設(shè)備上的文本展示效果九巡,那么看到這里就已經(jīng)夠了。但是我需要的是 iOS 和 Android 展現(xiàn)出一模一樣的效果蹂季,所以光有行間距是不能滿足需求的冕广。主要的原因在前言也提到了,Android 設(shè)備上的文字上下默認(rèn)留白(上一節(jié)圖中藍(lán)色和紅色重疊的部分)和 iOS 設(shè)備上的是不一致的:

26-G

左側(cè)是 iOS 設(shè)備偿洁,右側(cè) Android 設(shè)備撒汉,可以看到同樣是顯示 20 號(hào)的字體,安卓的行高會(huì)偏高一些涕滋。在不同的 Android 設(shè)備上使用的字體不一樣睬辐,可能還會(huì)出現(xiàn)更多的差別碰缔。如果不想辦法抹平這差別需频,就不能真正意義上實(shí)現(xiàn)雙端一致了敬拓。

這時(shí)候我們可以通過設(shè)置 lineHeight 來使得每一行文本的高度一致设哗,lineHeight 設(shè)置為 30pt 的情況下,一行文本高度一定是 30pt茫因,兩行文本高度一定是 60pt癞埠。雖然文字的渲染上會(huì)有細(xì)微的差別芽世,但是布局上的差別將被完全的抹除黔酥。lineHeight 同樣可以借助 NSAttributedString 來實(shí)現(xiàn),示意代碼:

運(yùn)行一下觀察效果:

27-A

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

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

修正文字在行中展示的位置棵帽,我們可以用 baselineOffset 屬性來搞定。這個(gè)屬性十分有用渣玲,在實(shí)現(xiàn)上標(biāo)下標(biāo)之類的需求時(shí)也經(jīng)常用到它逗概。經(jīng)過調(diào)試,發(fā)現(xiàn)最合適的值是 (lineHeight - label.font.lineHeight) / 4(尚未搞清楚為什么是除以 4 而不是除以 2忘衍,希望知道的老司機(jī)指點(diǎn)一二)逾苫。最終的代碼示例如下:

貼一下在不同字號(hào)和行高下的展示效果:

27-B

行高和行間距同時(shí)使用時(shí)的一個(gè)問題

不得不說行高和行間距我們都已經(jīng)可以完美的實(shí)現(xiàn)了,但是我在嘗試同時(shí)使用它們時(shí)枚钓,發(fā)現(xiàn)了 iOS 的一個(gè) bug(當(dāng)然也可能是一個(gè) feature铅搓,畢竟不 crash 都不一定是 bug):

27-C

著色的區(qū)域都是文本的繪制區(qū)域,其中看上去是橙色的區(qū)域是 lineSpacing搀捷,綠色的區(qū)域是 lineHeight星掰。但是為什么單行的文本系統(tǒng)也要展示一個(gè) lineSpacing 啊D壑邸氢烘?坑爹呢這是!家厌?

好在我們通常是行高和行間距針對(duì)不同的需求分別獨(dú)立使用的播玖,它們?cè)诜珠_使用時(shí)不會(huì)觸發(fā)這個(gè)問題。所以在 VirtualView-iOS 庫中饭于,我暫且將高度計(jì)算的邏輯保持和系統(tǒng)一致了蜀踏。

總結(jié):

至此,成功的為 VirtualView-iOS 添加了對(duì) lineHeight 屬性的支持镰绎,更多的實(shí)現(xiàn)細(xì)節(jié)大家可以到開源庫中直接看源代碼脓斩。希望我們的 Tangram 方案可以更加完善,幫助更多的人一次開發(fā)兩端同時(shí)使用畴栖,用一塊七巧板拼出大千世界随静。

補(bǔ)充:

NSFontAttributeName                設(shè)置字體屬性,默認(rèn)值:字體:Helvetica(Neue) 字號(hào):12
NSForegroundColorAttributeNam      設(shè)置字體顏色吗讶,取值為 UIColor對(duì)象燎猛,默認(rèn)值為黑色
NSBackgroundColorAttributeName     設(shè)置字體所在區(qū)域背景顏色,取值為 UIColor對(duì)象照皆,默認(rèn)值為nil, 透明色
NSLigatureAttributeName            設(shè)置連體屬性重绷,取值為NSNumber 對(duì)象(整數(shù)),0 表示沒有連體字符膜毁,1 表示使用默認(rèn)的連體字符
NSKernAttributeName                設(shè)定字符間距昭卓,取值為 NSNumber 對(duì)象(整數(shù))愤钾,正值間距加寬,負(fù)值間距變窄
NSStrikethroughStyleAttributeName  設(shè)置刪除線候醒,取值為 NSNumber 對(duì)象(整數(shù))
NSStrikethroughColorAttributeName  設(shè)置刪除線顏色能颁,取值為 UIColor 對(duì)象,默認(rèn)值為黑色
NSUnderlineStyleAttributeName      設(shè)置下劃線倒淫,取值為 NSNumber 對(duì)象(整數(shù))伙菊,枚舉常量 NSUnderlineStyle中的值,與刪除線類似
NSUnderlineColorAttributeName      設(shè)置下劃線顏色敌土,取值為 UIColor 對(duì)象镜硕,默認(rèn)值為黑色
NSStrokeWidthAttributeName         設(shè)置筆畫寬度,取值為 NSNumber 對(duì)象(整數(shù))返干,負(fù)值填充效果兴枯,正值中空效果
NSStrokeColorAttributeName         填充部分顏色,不是字體顏色犬金,取值為 UIColor 對(duì)象
NSShadowAttributeName              設(shè)置陰影屬性念恍,取值為 NSShadow 對(duì)象
NSTextEffectAttributeName          設(shè)置文本特殊效果,取值為 NSString 對(duì)象晚顷,目前只有圖版印刷效果可用:
NSBaselineOffsetAttributeName      設(shè)置基線偏移值峰伙,取值為 NSNumber (float),正值上偏,負(fù)值下偏
NSObliquenessAttributeName         設(shè)置字形傾斜度该默,取值為 NSNumber (float),正值右傾瞳氓,負(fù)值左傾
NSExpansionAttributeName           設(shè)置文本橫向拉伸屬性,取值為 NSNumber (float),正值橫向拉伸文本栓袖,負(fù)值橫向壓縮文本
NSWritingDirectionAttributeName    設(shè)置文字書寫方向匣摘,從左向右書寫或者從右向左書寫
NSVerticalGlyphFormAttributeName   設(shè)置文字排版方向,取值為 NSNumber 對(duì)象(整數(shù))裹刮,0 表示橫排文本音榜,1 表示豎排文本
NSLinkAttributeName                設(shè)置鏈接屬性,點(diǎn)擊后調(diào)用瀏覽器打開指定URL地址
NSAttachmentAttributeName          設(shè)置文本附件,取值為NSTextAttachment對(duì)象,常用于文字圖片混排
NSParagraphStyleAttributeName      設(shè)置文本段落排版格式捧弃,取值為 NSParagraphStyle 對(duì)象
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赠叼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子违霞,更是在濱河造成了極大的恐慌嘴办,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件买鸽,死亡現(xiàn)場(chǎng)離奇詭異涧郊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)眼五,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門妆艘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來彤灶,“玉大人,你說我怎么就攤上這事双仍∈嘞#” “怎么了桌吃?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵朱沃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我茅诱,道長(zhǎng)逗物,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任瑟俭,我火速辦了婚禮翎卓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摆寄。我一直安慰自己失暴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布微饥。 她就那樣靜靜地躺著逗扒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪欠橘。 梳的紋絲不亂的頭發(fā)上矩肩,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音肃续,去河邊找鬼黍檩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛始锚,可吹牛的內(nèi)容都是我干的刽酱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瞧捌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼棵里!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起察郁,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤衍慎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后皮钠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稳捆,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年麦轰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乔夯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砖织。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖末荐,靈堂內(nèi)的尸體忽然破棺而出侧纯,到底是詐尸還是另有隱情,我是刑警寧澤甲脏,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布眶熬,位于F島的核電站,受9級(jí)特大地震影響块请,放射性物質(zhì)發(fā)生泄漏娜氏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一墩新、第九天 我趴在偏房一處隱蔽的房頂上張望贸弥。 院中可真熱鬧,春花似錦海渊、人聲如沸绵疲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盔憨。三九已至,卻和暖如春朝捆,著一層夾襖步出監(jiān)牢的瞬間般渡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工芙盘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驯用,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓儒老,卻偏偏與公主長(zhǎng)得像蝴乔,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子驮樊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容