深入探討UILabel確切行數. Calculating the exact number of lines of a UILabel

Michael Revlis
5 min readJan 2, 2022

最近晚輩遇到了一個需求「某段文字要根據內容決定顯示的高度,一行或兩行,但最多僅顯示兩行,超過的話下方要長出一個『顯示更多』的按鈕,點擊後再展開...」

先不討論這樣的設計到底是不是一個 mobile app 適用的體驗,既然需求開出來了工程師就要去解決它。晚輩找了幾篇相關的文章,都能解決這樣的需求,但為了避免知其然而不知其所以然,這裡還是把詳細的程式碼一一解釋一下,並進一步做延伸思考。

這篇 StackOverFlow 是我覺得寫得比較完整的,以它為範例進行改寫:

第6行是取得字體大小,避免因為給定了 UILabel 其他字型字體設定,跟預設有差異而導致計算上的誤差。

第9行是要指定這個 UILabel 的大小,因為我們想要知道的是有多少行高,所以寬度就以自身寬為準,高度給定 CGFloat.greatestFiniteMagnitude,顧名思義就是給了一個很大很大的值(官方說明看此),讓 UILabel 可以想長多高就多高幾乎不受限制。

而第11行這邊使用了 NSString 的 boundingRect(with: options: attributes: context:) 這個 method,是根據指定的參數設定在指定的範圍內畫出圖像內容(官方說明)。因為是 NSString,所以才有第4行的轉型,這邊偷懶硬轉,直接講解 boundingRect 的參數。size 就是上面第9行的邊界大小,因為寬是固定的,所以會根據內容往下延長。

第12行的 options 指的是繪出文字的設定,這邊使用的是 .usesLineFragmentOrigin,是在計算多行文字內容時官方所推薦的選項。NSStringDrawingOption 目前總共有四種設定,有興趣的可以再去看看官方資料

To correctly draw and size multi-line text, pass usesLineFragmentOrigin in the options parameter.

第13行的 attributes 就是 NSStringAttributedString 中所需要的屬性,這邊使用的是第6行的 attributes,但僅包含該 UILabel 的字型。這邊就會有人提問了:「是否有字型大小以外的參數會影響到文字個高度呢?」,答案是有的,如果該 UILabel 使用是 attributedText 來顯示文字,那我們可以透過 NSMutableParagraphStyle 來給定諸如行距等設定。所以這邊留了一個伏筆,也就是被註解掉的第7行,我們去取得當前這個 UILabel 的 attributedText 的屬性。NSAttributedString 有一個 attributes(at:effectiveRange:) 的 method 可以抓出指定範圍內的文字屬性,其中 at (location) 是要給從哪邊開始,這邊因為 demo 方便給定 0 其實是很危險的寫法,因為當 string 是空字串時 location 0 其實就超過空字串自身的範圍了。而 effectiveRange 這邊因為我們是要拿整段文字,就不指定範圍了直接給 nil。

index
The index for which to return attributes. This value must lie within the bounds of the receiver.
aRange
Upon return, the range over which the attributes and values are the same as those at index... If you don't need this value, pass NULL.

而最後一個參數 context 是指其它的設定,如果有知道的話可以加上去,但如果是要寫一個 通用 的 method 來計算 UILabel 行數,那大可忍受這樣的不精確。

後面14到20行其實就是簡單地把總高度除以該字型單行行高,取無條件進位後轉換成 Int 而已。

再深入想一想

第7行我們擷取了 attributedString 的屬性來計算,那如果 attributedString 有兩段以上不同的屬性設定呢?」「如果使用 NSMutableParagraphStyle 指定單行行高呢?這樣用 UIFont.lineHeight 還適合嗎?」這真是好問題,例如果們給了前後段文字不同的 UIFont 時,第7行的程式碼計算的結果就會跟我們的預想有出入,畢竟條件不一樣了。當我們抽絲剝繭深入去看到每一個環節的細節時就會發現,其實沒有一個通用的方法可以完美地計算出所有可能,都是取捨的問題,如果想要寫一個 通用 的 method,勢必只能符合大部分的情況;如果是要符合某個特殊案例,那還是要乖乖針對這個案例去解析。而當務之急還是先理情眼前的需求,找出適切的方案(當然,如果能進一步與需求單位溝通那也是一條路)。

後記

這篇文章其實沒有講到甚麼多深入的東西,反而是一直貼官方文件出來,其實是希望初學者樣成一個好習慣,多去看看寫下的每一行程式碼其背後的意涵。現在網路上找資料很方便,看到有人寫 code 遇到問題是貼上,跑不動換下一個答案貼上;或是不仔細看 error message 的內容,找到甚麼答案改什麼,但其實那不一定適用當前遇到的問題。知道自己寫的 code 是怎麼跑的,出錯時才有頭緒可能是哪邊出問題了,而不是一頭霧水。

--

--