iOS自动布局
Storyboard中用Auto Layout的“Bug”
在storyboard中使用自动布局,根控制器为一个UITableView,所有的cell的行高根据cell内容的多少动态变化(类似微博、微信朋友圈等应用)。
链接地址:demo
据此,我根据
Cell
的内容获取Cell
内的label
和UIImageView
的frame,然后在layoutSubview方法中根据frame来计算最终的行高,然后,在controller中通过代理方法拿到行高。
这是很常规的一个开发思路,可是在storyboard开发中使用prototype(原型)cell的方式来实现却遇到了问题...
1.首先,我在StoryBoard里面使用prototype(原型)cell,而且没有改ViewController属性检测器中的simulation Metrics
的size
<img src="https://img.haomeiwen.com/i2473291/318b2b21411c5051.png" width="400" height="400" alt="不设置此处的尺寸"/>
2.然后,我给cell中的所有的子控件设置了约束,并且确认无误,没有警告和错误,然后我update了一下所有控件的frame(注意:这里如果不更新所有控件的frame的话,storyboard便会给你报黄色警告,提示你期望的大小和真实的大小不相同,截图如下
<img src="https://img.haomeiwen.com/i2473291/2a660988870a8925.png" width="400" height="300" alt="ExpectedFrame和ActualFrame的警告"/>
3.解决上面的办法也很简单,点击警报,点击Fix(修复)或者选中所有的控件,点击下方设置约束按钮中最右边的一个,选择"Update Frame"就可以了
<img src="https://img.haomeiwen.com/i2473291/3292bacfb3423660.png" width="400" height="400" alt="修复上述警报的冲突"/>
4.设置完约束,我需要分别实现模型中的逻辑,和自定义的cell中的逻辑
4.1. 模型类从网络获取数据,使用的是异步子线程,下载完成后回调,切换主线程给ViewController中的数据数组赋值。
4.2. 给自定的cell内的子控件赋值写在了
UITableViewDatasource
的返回cell的方法中,但是,要求cell内的副标题Label的显示或不显示,取决于主标题Label的行数,如果主标题只显示1行,那么就显示副标题,相反,就不显示副标题。
这个逻辑才是重点
我在cell的layoutSubviews
方法中获取主标题的frame,可是,无论如何改变主标题Label的约束,通过layoutSubviews
方法获取到的frame始终不变。这里引起了我的怀疑,因为layoutSubviews
方法的解释是当父控件的frame发生改变的时候,会调用此方法。但是实际来看在layoutSubviews
方法中cell内的控件的frame仍然未发生变化,还是storyboard中Update Frames之后的值!!
副标题不正常的情况但是当执行完
layoutSubviews
方法后,界面就已经能够正常地显示出来了,并且大小都正常,但是在layoutSubviews
方法里打印的Label的frame却不正确,这就很奇怪!
于是,我继续猜想,这可能是由于约束设置完成之后,需要通过很多计算,而产生一个时间差,所以就使得在layoutSubviews
方法执行的那一瞬间,无法立即计算出主标题的正确的frame。
接着,我寻找其他的思路,发现有一个方法layoutIfNeeded
,这个方法的解释是:Lays out the subviews immediately.在调用这个方法后,立即布局该控件的子控件,因此,我试着在调用主标题Label的getter方法之前,调用layoutIfNeeded
这个方法,结果就成功了。
得出总结:当父控件的frame发生变化的时候,会调用它的layoutSubviews
方法,但是,此时我们的约束还没有计算好所有子控件的frame值,所以,要在layoutSubviews
方法内获取子控件的frame,可能就会拿到一个错误的frame值
在解决这个Bug的同时,还遇到一个小插曲,当属性检测器使用的是默认的大小的时候,如果勾选了storyboard中的ViewController属性检测器的Refreshing中的Enabled选项,则会出现在刚开始进入应用的时候,就会加载self.refreshControl 的title 内容
使用storyboard默认的大小但是当我不进行其他任何的操作,而仅仅只是把属性检测器中模拟器的size属性设置为一个其他的大小时,就不会有上述的效果了
设置一个指定的尺寸之后.png