NSLayoutConstraint|NSLayoutConstraint (VLF) + ScrollView

在平时开发中ScrollView扮演非常重要的角色,经常需要给ScrollView添加一些ContentViews, 并能上下左右滑动, 但是当你添加完这些ContentViews,并设置好约束之后,惊讶的发现ScrollView上没有任何内容或ScrollView并不能滑动。一阵惊慌中,疯狂开启NSLog模式,茫然发现frame为0,contentSize为0,什么情况?
导致这种现象的原因不是我们的约束设置有问题,而是在AutoLayout模式下ScrollView计算ContentSize的机制不一样的,ContentSize是依据ContentViews的宽和高自动计算出来的,不需要我们手动设置。在ViewDidLoad之后,系统会根据ContentViews情况重新算出ScrollView的ContentSize。
我们在添加ScrollView时,只是给出了到屏幕各个边的约束为0,却没有给出具体的宽度和高度, 所以无法确定ContentSize大小,自然不能滑动。建议在ScrollView上先添加一个UIView,UIView的大小和ScrollView一样,然后在UIView上添加ContentViews, 这样系统就会准确的算出ScrollView的ContentSize, 保证ScrollView能滑动起来。
NSLayoutConstraint有两个添加constraint的方法:

+ (NSArray<__kindof NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(nullable NSDictionary *)metrics views:(NSDictionary *)views; +(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;

WithVisualFormat方法比WithItem更加形象化、简洁化,建议多使用可视化方法。
在给ScrollView添加子视图之前,建议先弄清楚ScrollView上一共有多少个子视图,并使用可视化方法一并写出来,告诉系统我需要添加这些视图,等之后这些视图的尺寸确定下来后,你就可以计算contentSize了。否则,系统不知道你一共有多少个子视图,也不知道第一个和最后一个子视图是谁,这样就难以计算contentSize。
我现在需要在ScrollView上添加一个UIImageView,一个UIView, 一个UILabel。
在屏幕中位置如下:
ScrollView:
scrollView大小为屏幕大小,背景色white
ContentView:
contentView大小同scrollView大小,背后色red
UIImageView:
. top 距离底部60,
. left 距离左边5
. right 距离右边5
. height 高度100
背景色green
UIView:
. top 距离上面10
. left 距离左边10
. right 距离右边10
. height 高度80
背景色purple
UILabel:
. top 距离上面8
. left 距离左边8
. right 距离右边8
. height 高度根据文字内容自动调整
背景色yellow
效果图如下:

NSLayoutConstraint|NSLayoutConstraint (VLF) + ScrollView
文章图片
screenShot.png
给ScrollView和ContentView添加约束的代码如下:
self.automaticallyAdjustsScrollViewInsets = NO; UIScrollView *scView = [[UIScrollView alloc] init]; scView.backgroundColor = [UIColor whiteColor]; scView.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:scView]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[scView]-(0)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(scView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[scView]-(0)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(scView)]]; UIView *contentView = [[UIView alloc] init]; contentView.backgroundColor = [UIColor redColor]; contentView.translatesAutoresizingMaskIntoConstraints = NO; [scView addSubview:contentView]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(contentView)]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];

注意:由于ScrollView可以垂直和水平滑动,所以我们指定ContentView的宽度为屏幕宽度后,scrollView就只能垂直滑动了。
在添加子试图之前需要一次性将所有的子视图列举出来,并设置好子视图之间的上下间距
UIImageView *imageView = [[UIImageView alloc] init]; imageView.backgroundColor = [UIColor greenColor]; imageView.translatesAutoresizingMaskIntoConstraints = NO; [contentView addSubview:imageView]; UIView *boxView = [[UIView alloc] init]; boxView.translatesAutoresizingMaskIntoConstraints = NO; boxView.backgroundColor = [UIColor purpleColor]; [contentView addSubview:boxView]; UILabel *label = [[UILabel alloc] init]; label.backgroundColor = [UIColor yellowColor]; label.translatesAutoresizingMaskIntoConstraints = NO; [contentView addSubview:label]; label.text = @"起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字起个很长很长的名字"; label.numberOfLines = 0; [contentView addSubview:label]; NSDictionary *tmpViewsDictionary = @{@"imageView":imageView, @"boxView":boxView, @"label":label, }; [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(60)-[imageView(100)]-(10)-[boxView(80)]-(8)-[label]-(8)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(5)-[imageView]-(5)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(10)-[boxView]-(10)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(8)-[label]-(8)-|" options:0 metrics:nil views:tmpViewsDictionary]];

注意:需要时刻提醒自己准确的指出每个子视图的宽和高,这样ScrollView才能算出ContentSize。UILabel由于会根据text内容自动算出高度,所以不需要明确列出label的高度,而UIView如果不给出其高度的话,是无法正确显示的。
最后如果我们将boxView的高度改的足够高,如800,就可以看见ScrollView能像预期的那样滑动起来了
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(60)-[imageView(100)]-(10)-[boxView(800)]-(8)-[label]-(8)-|" options:0 metrics:nil views:tmpViewsDictionary]];

【NSLayoutConstraint|NSLayoutConstraint (VLF) + ScrollView】最后吐槽下:NSLayoutConstraint真的很难用。

    推荐阅读