【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来做的!!!!厉害了word哥!!!!!来一波传送——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一把去,有问题请留言,撸完之后看=_=。