Cell 動態行高文字顯示不全問題探索

Dast1發表於2021-02-07

問題概述

使用的是”預估行高+自動佈局“的方法實現動態行高(適用於 iOS7.0 以後系統)。

預估行高:

self.gTV.estimatedRowHeight = 90;
self.gTV.rowHeight = UITableViewAutomaticDimension;

自動佈局,又叫 autolayout,為了使文字可以多行顯示,需要保證如下設定:

  • 設定 label 的 numberoflines 為 0
  • 對 label 進行上左下右的完整約束

在專案實現過程中,遇到了文字內容被截斷最後一行一小部分,無法完全顯示的問題。

為了復現專案中遇到的此問題並找到原因,做了如下嘗試:

一、新建工程

新建工程測試,cell上下約束完備,底部高度約束 contentLblBtmCon 為>=9,優先順序預設1000。發現預估行高是正常的。

效果如下:

Simulator Screen Shot - iPhone SE (1st generation) - 2021-02-07 at 15.27.32

二、嘗試復現問題

隱藏系統cell分割線:self.gTV.separatorStyle = UITableViewCellSeparatorStyleNone;

同時,在自定義cell中重寫 setFrame方法實現分割線效果,結果發現文字開始顯示不全了!

- (void)setFrame:(CGRect)frame{
    frame.size.height -= 8;
    [super setFrame:frame];
}

約束報錯如下:

2021-02-07 14:56:37.416314+0800 DynamicCellHeightTest[60202:8764494] [LayoutConstraints] Unable to simultaneously satisfy constraints.
	Probably at least one of the constraints in the following list is one you don't want. 
	Try this: 
		(1) look at each constraint and try to figure out which you don't expect; 
		(2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x6000033a5810 UILabel:0x7f898d0072c0.height == 21   (active)>",
    "<NSLayoutConstraint:0x6000033a59f0 UILabel:0x7f898d016da0.height >= 20.5   (active)>",
    "<NSLayoutConstraint:0x6000033df2a0 V:|-(16)-[UILabel:0x7f898d0072c0]   (active, names: '|':UITableViewCellContentView:0x7f898d0061f0 )>",
    "<NSLayoutConstraint:0x6000033df340 V:[UILabel:0x7f898d0072c0]-(NSSpace(8))-[UILabel:0x7f898d016da0]   (active)>",
    "<NSLayoutConstraint:0x6000033df430 V:[UILabel:0x7f898d016da0]-(>=9)-|   (active, names: '|':UITableViewCellContentView:0x7f898d0061f0 )>",
    "<NSLayoutConstraint:0x6000033ddb80 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7f898d0061f0.height == 86   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x6000033a59f0 UILabel:0x7f898d016da0.height >= 20.5   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

通過 log 可知,內容 label 的高度約束被捨棄了,因此會出現內容顯示不全的問題。模擬器執行效果:

Simulator Screen Shot - iPhone SE (1st generation) - 2021-02-07 at 15.28.30

嘗試解決

修改contentLblBtmCon優先順序為High(750)

截圖2021-02-07 下午3.14.06

結果還是不太行:

Simulator Screen Shot - iPhone SE (1st generation) - 2021-02-07 at 15.29.15

修改contentLblBtmCon優先順序為Low(250)

截圖2021-02-07 下午3.18.00

效果如下:

Simulator Screen Shot - iPhone SE (1st generation) - 2021-02-07 at 15.26.05

可見,此時內容可以顯示全了,Xcode 也不報錯了。但是內容距離 cell 底部的距離太小了,並沒有大於 9。猜測:這個底部約束因為優先順序是Low,所以被系統捨棄,使得內容可以顯示完整,同時導致內容距離 cell 底部的距離太小。

但是,當我嘗試設定底部約束的為 >= 9+8=17,再執行,居然就是我想要的效果:

Simulator Screen Shot - iPhone SE (1st generation) - 2021-02-07 at 15.40.45

Simulator Screen Shot - iPhone SE (1st generation) - 2021-02-07 at 15.40.53

分析:系統先在 setFrame 生效之前,對 cell 內的上下所有約束進行行高預估。計算拿出結果後快取。在 cell 顯示之前,setFrame 生效,此時,cell 在之前預估行高的基礎上,根據約束重新佈局,捨棄了內容 label 的高度約束,導致內容顯示不全。

當我們把底部約束的優先順序降低到 Low 時,cell 在之前預估行高的基礎上,根據約束重新佈局,捨棄的就是低優先順序的底部約束了,因此才能看到低優先順序底部約束開始生效,後來因 setFrame 減小了高度,導致底部間隔變小的效果。此時,我們將計就計,把底部約束增加 cell 間隔高度(8),即可得出我們想要的效果!

小結

重寫 cell 的 setFrame 方法改變 cell 高度來實現分割線效果時,可能導致多行 label 顯示不全,此時,可以通過降低底部約束優先順序為 Low + 增加底部約束的值(cell 間距),來實現想要的文字多行顯示效果。

其他解決思路

當然,為了實現行分割線效果,我們也可以在自定義 cell 的底部手動新增一個 UIView 子檢視,高度設定為 cell 間隔高度,顏色改為與 UITableview 背景色一致(與 cell 背景色不同),也能達到同樣的效果。這種方法就不會因為重寫 cell 的 setFrame 方法導致多行文字顯示不全了。但是,當 cell 有選中效果或左滑刪除效果時,相關效果就不是很好了,因為分隔線 View 屬於 cell 的一部分被一起選中或移動,看起來效果有點不太好。

好了,這次的探索就到這裡了。下面附上測試用的原始碼,歡迎 star!3Q!

原始碼地址

OCDailyTests/DynamicCellHeightTest

相關文章