之前翻译过一篇《如何在AutoLayout 中使用UIScrollView (多个ContentView)》(以下简称《如何》)。在这篇文章中很详细地解释了在UIScrollView中使用自动布局的种种限制和注意事项。
我本来以为这已经解释得很清楚了。但是仍然有读者说文中示例虽然可行,但在其他界面上却无法做出同样的效果。
考虑到也许是文中例子过于复杂,使得有的读者看虽然看得懂,照着文中步骤也能做出效果,但由于没有真正理解文中的原意,一旦离开例子实现自己的UI就犯难了。
我们另外举一个简单例子,以此来充分理解UIScrollView在Autolayout下使用的基本原则。
《说明》一文中列举了很多原则,但最根本的其实只有以下两条:
原则1:UIScrollView的size依赖于subviews
首先在画布中拖入一个UIScrollView。用Pin按钮,随意设置其布局。甚至不用设置。
因为《如何》一文中的第一个原则是:UIScrollView的布局依赖于subviews。因此我们设置UIScrollView的布局约束是没有用的。我们只需要将subviews的size都明确下来,这条原则自然不成问题。
UIScrollView的size(即contentSize)则根据subviews所占据的size来计算。当然,如果contentSize的内容不足以布满整个UIScrollView时,滚动条将不会出现,UIScrollView也不会滚动。
原则2:subviews的size不能依赖于UIScrollView
比如你设计这样一个布局:
UIScrollView包含两个subview:UILabel和UITextField。
UILabel的Pin约束设置如下:
注意,这里我们固定了UILabel的宽度为100,而Label的高度也是固定的(lines设置为1)。这是可行的,因为它的宽高都没有依赖于UIScrollView了。
但我们不可能所有控件都使用固定的宽高,那样就不叫做“自动布局了”。
例如,UITextField,我们准备设置它的Pin约束为:
这样,UITextField没有固定的宽高了。这样说也不对,其实UITextField是有固定高的,对于一个使用了默认边框类型(圆角)的UITextField来说,它的高度固定为30。但对于宽度则是由左边缘和右边缘决定。
从上图可知,它的左边缘距离左边的UILabel 8个像素,右边缘距superview的右边缘14个像素。这样UITextField的宽度应该由右边缘的x减左边缘的x得到。
由于左边的UILabel为固定宽度,所以UITextField的左边缘位置也是固定的。但右边缘的x则是相对于superview而定的,这就不可能固定。
我们知道在Xcode6的自适应布局上,view的大小实际上是根据设备而变的,在iPhone上是一个size,在iPad上又是另一个size。
但无论如何,我们的UITextField无论在何种设备上都能自适应。无非是在iPhone上略显窄一点,在iPad上则宽一些。
但是当你设置这样的约束后,你会发现出现了红色的布局警告:
这表示UITextField的宽度在运行时会比布局时短的多。运行效果如下:
这根本不是我们想要的样子。
这是因为UITextField违反了原则2:subviews的size不能依赖于UIScrollView。
如上所述,UITextField的宽度取决于superview的宽度。但和你理解的不同,UITextField的superview是UIScrollView而不是ViewController的view。
为什么subviews 的size不能依赖于UIScrollView?
很简单,因为原则1:UIScrollView的size依赖于subviews。如果subviews的size再依赖于UIScrollView,则布局引擎就混乱了。就像我们解一个方程式,x的求解要先知道y的值,而y的值却要先明确x的值,这个就无解了。
要解决这个问题,我们需要把UITextField的宽度明确,起码不能和靠不住的UIScrollView的size相关。我们可以这样设置UITextField的宽度约束:
找到UITextField的size面板,双击Tailing Space to这条约束(如果单击则仅仅是卸载而不是删除):
然后按delete键。
右键,从UITextField拖到ViewController的view,然后选择Equals Widths。然后双击新建的这条约束,修改为如下样子:
注意First Item和Second Item项,不要将二者搞反了。
运行程序:
iPhone和iPad上UITextField都能很好地适应。
对于高度的布局显示也是一样的,笔者就不一一举例了。总之,不得违反上述原则。