【Bug】UITableViewCell AutoLayout重布局約束沖突

CC

開了簡書10個月,一直說要記錄一些東西,結果總有各種借口拖延。今天不打LOL,完成開篇大任=_=


BUG

最近項目中遇到一個問題:對UITableViewCell采用AutoLayout自動布局,由內容撐起高度。而內容是需要網絡返回的,所以在一開始進入VC,會向服務器發出請求,同時進行了初始繪制;在接收到服務器返回數據時,UITableView進行reloadData,同時根據數據重新繪制。而重新繪制導致UITableViewCell高度與之前不同,這時就報了約束沖突。但是UI呈現完全沒問題。

約束沖突

起始約束是在-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier初始化方法里的調用的;而當網絡數據返回時,UITableView reloadData;-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath方法會調用cell中一個塞數據的方法,在塞數據方法最后會調用setNeedsUpdateConstraints方法;而在-(void)updateConstraints方法中,會根據塞入的數據,來進行約束的update,這里用的Masnory庫,調用的方法是- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block。這個約束沖突就報在了-(void)updateConstraints方法中。

分析與解決

根據AutoLayout的報錯,發現其他都沒毛病,就最后一項有問題:height == 190.5?沃德法克?打開Debug View看一眼:

289.5 != 190.5

就說不可能這么矮=_=,190.5是初始沒有內容時的高度,而289.5是網絡數據返回后,根據數據重布局的高度。那么沖突應該就是報在這個位置了。

一個腦殘的猜測

就是是不是因為網絡數據返回太快了,太短時間內多次更改constrains,再updateConstraints會導致沖突?(網絡數據回來100ms以內,好吧,不快=_=)

妥,setNeedsUpdateConstraints延時1秒執行。

果然不報錯了!BUT!文字圖片之間的間距被擠壓,重疊到了一起是鬧哪樣啊?

但是重疊的畫面出現后,再次reloadData,就沒問題了。請求一次數據要reloadData兩次?這不是一名合格的程序員應該做的事情,你知道reloadData有多耗費資源啊(- 不知道。 - 小伙子,可以的,你被開了)!

一個不腦殘的事實

debug view Hierarchy里面給的清清楚楚,contentView.height約束的prority是1000。所以就是你在高度190.5時候,撐高contentView,超出了190.5導致約束沖突報錯了唄。于是:

解決方案一——撲街

把撐高contentView的約束全部設置priority為999。

修改約束proroty

哈哈哈!果然不報錯了。

=_=||| ?尷尬,約束是不沖突了,但是UI呈現有問題了,又把我文字之間的間隔給抹掉了,再戰!

解決方案二——撲街

直接拿掉contentView的height約束,沒有這個約束,看你還怎么沖突,嘿嘿嘿。

在updateConstraints方法開始的伊始,用一個for循環,判斷去除contentView的height約束。

結果,很遺憾,不僅約束報錯,UI呈現也完全不對了。

是不是沒去除干凈?打個斷點,po一下contentView的constrains,發現果然還有東西:

NSAutoresizingMaskLayoutConstraint還在

哦,看來UITableView不是用AutoLayout布局的,這個AutoresizingMask有點可疑,查,給朕徹查!

學習了一下Autoresizing,三種自適應布局方式之一(另外兩種是layoutsubview中手動布局和autolayout),我的理解就是frame布局上增加了一定的自適應。

然后——UITableView用的Frame布局!!!滾動動畫巴啦巴啦的也是靠著frame和bounds來做的?。。。柡α藈ord哥?。。。?!來一波傳送——https://www.objc.io/issues/3-views/scroll-view/

建議objc的東西都學習一下啦,爆強!英語沒過六級的同學(沒錯,就是我)可以搜下Objc中國咯。

解決方案三——撲街

修改contentView.height約束的prority為999。

修改contentview約束prority

其實根據方案二的結果已經猜到了——不僅約束報錯,UI呈現也完全不對了。其他cell 的contentview.height.prority都是1000,就你自己是999,坑定有問題啊。當然,可以所有cell的contentview.height.prority都設為999,但是考慮cell復用問題,改動面太大,所以就放棄了。

正確解決方案

回到一開始的腦殘猜測。延時setNeedsUpdateConstraints執行,再次reloaddata,就沒問題。那么UITableViewCell設置contentView的height的時間應該是在updateConstrains之后,所以導致高度沒有實時變化,從而沖突(- 機智如我! - 其實一開始報約束沖突的時候就該想到的,報沖突在updateConstrains,但是高度沒有實時更新。- 你走開?。?。

然后就查了一下setNeedsUpdateConstraints機制,然后就引出了setNeedsLayout的問題。兩者的區別是個毛毛?

從stackoverflow上找到一份答案,鏈接沒存,大概意思就是——兩者都是表示需要重新布局,而且都是標記需要,并不會立即執行,會在下一個循環里執行;setNeedsUpdateConstraints對應-(void)updateConstraints,setNeedsLayout對應- (void)layoutSubviews。BUT,setNeedsUpdateConstraints會調用-(void)updateConstraints之后再調用- (void)layoutSubviews,而setNeedsLayout只會調用- (void)layoutSubviews(本人親測這樣子的)。

我的理解:setNeedsUpdateConstraints就是更新約束,setNeedsLayout就是更新frame,約束的底層其實就是更改frame,所以setNeedsUpdateConstraints會同時調用兩者(- 不對。- 你留言。)。

說了這么多,終于到解決方案了。最終方案是還在stackoverflow上找到的,一個來自于三年前的帖子——http://stackoverflow.com/questions/19132908/auto-layout-constraints-issue-on-ios7-in-uitableviewcell。

這篇帖子給了兩個解決方案,一個是設置:self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;

另外一個是:在更新約束之前,設置self.contentView.bounds = CGRectMake(0, 0, 99999, 99999);

第一種方案我這邊不可行,根據方案二里的log,contentview的寬、高已經設置了Autoresizing,這個有毛用?反正我設置了,沒用。

第二種方案。。。

(- 啊啊啊啊啊啊!直接改高度,我他喵的怎么就沒想到?。?- 因為你臉大。 - 臉大有福!你羨慕不來的!啊啊啊?。。『脷獍。。?! - 。。。)

昂,在更新約束之前,直接把contentView的寬度、高度值設置為大于等于約束之后的寬高就解決問題了。UITableViewCell給contentView計算并設置寬高是在- (void)layoutSubviews方法中進行的,而-(void)updateConstraints- (void)layoutSubviews之前執行,從而更新約束超出之前寬高值,導致沖突;而在-(void)updateConstraints之前擴增一下寬高,就不會沖突,而在- (void)layoutSubviews方法中又重新計算了高度賦值,所以UI呈現也沒問題。

BUT,為毛設置高度超出約束之后應得的值就沒問題咧(如本例中,需要更新約束前,設置contentView.height的值大于等于289.5)?約束把高度撐起來不行?壓縮反而可以?講道理的話,不應該設置的剛剛好才行么?

一位老司機告訴我,可能是這個樣子的:

你把高度設低了,那么你布局的時候就超出去了,布局都完不成;而你把高度設高了,那么你布局最起碼能在這個view里完成。

BUT,我的約束是從上到下,從top到bottom串聯起來的,你設置height高了,肯定會有一個部分被拉伸,而所有的priority都是1000,講道理的話,也應該報沖突啊。

求老司機解答 =_=

總結

stackoverflow果然是一個神奇的網站,三年前的解答竟然解決了我的問題,古人誠不我欺也。

objc要好好學習,英文水平有待提升(現在屏幕分兩半,一半objc,一半百度翻譯)。

最后,竟然寫了這么多,LOL一把去,有問題請留言,擼完之后看=_=。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容