作为一个iOS 开发者,很多情况下会需要把一个和屏幕等宽的 contentview 添加到一个 scollrview 内部中。大多数的 app 需要有响应式布局,所以使用 Autolayout 可谓明智之选。第一次学习 scrollviews 时,你或许会觉得碉堡了。但它们奇怪的规则也会令人有点沮丧。
准备
你对 Xcode 和 interface builder 都足够熟悉。
在 ScrollView 内的 Content View
这篇文章重点在 autolayout,所以我要用各种方式来告诉你如何处理它。第一个方式是 interface builder,因为这是最直观的。
设置垂直滚动视图的 interface builder
创建一个新的单视图控制器应用程序,打开 storyboard,添加一个 scrollview 到 storyboard 上的ViewController 上。使 scrollview 填满整个 viewcontroller 的 view。然后使用 storyborad 右下角的 pin 菜单,给 scrollview 添加上、左、右、下的约束。确保你没有选中“constrain to margin”复选框。图1 显示了正确设置的 pin 菜单。这将添加 scrollview 和它的父视图之间的约束。
图1:给scrollview添加 上,左,右下的约束。
现在你想添加一个标准的 view 到 scrollview 上。就如你刚刚给 scrollview 添加上、左、右、下的约束那样,给这个新 view 也添加这些约束,并确保这些约束的值都是0。
当你完成后,通过选中 view 面板中的 view ,查看在右侧的 Size Inspector, 可以查看添加到 scrollview 和 Container view 的约束。图2 显示了内容视图, 但 scroll view 应该看起来也是一样的。
图2:Size inspector 显示在 scrollview 和 container view 上的约束。
ok,现在容器视图里已经有了一个 container view。有人会认为,因为我们给 scrollview 的边缘添加了上、左、下、右的约束,所以这个 container view 将会一直在 scrollview 的上面,并且拥有和窗口一样的宽度。但事实并非如此,因为 scrollviews 略有不同,这些约束定义了 scrollview 的 content size ,但因为我们的 view 没有一个确切的 width 或 height ,所以这个 content size 的 wide 和 tall 都是0。
通常我们都想要一个 scrollview 只能垂直滚动,所以并不想让 container view 比窗口宽,而高度我们通常是想要动态的,所以它会像其内部的 contents 一样高,稍后再说动态高度,现在将解决固定宽度的问题。
设置垂直滚动
我们要确保 containner view 的宽度不会超过 window 的大小,为此我们将 container view 和 main view (包含 ScrollView 的视图)设置成等宽,在 view inspector 中,按着 ctrl 拖拽 main view 到container view 并从弹出的菜单中选中 EqualWidths。图3 显示了被连接的2个视图。
图3 按着 Ctrl 拖拽 main view 向 container view
设置动态 Container view 高
最后一步,没有那么多的步骤指导。基本上,为了这项工作,container view 上所有的子视图必须要有一个高度。一些子视图可以使用它们固有的高度,通常是由它们的宽来决定的。一般地,你需要为任何没有包含文本的视图指定宽度和高度。这些包含文本的视图至少有一个指定的宽或 margins。
第一个和最后一个视图应该分别以 container view 的顶部和底部分别固定。例如 在图4中,注意在图4中所有的视图都有左和右的约束,这将决定每个视图的宽度。labels 不具有高度的约束,因为它将取决于其内容的大小。方形的视图有确切的高度约束。还要注意所有的视图顶部和底部之间有一个显示的垂直约束。综合这些就能决定 contanier view 的高,还有scrollview 的 content size。
在图4中显示如下约束。
- Top Label
- 距 container view 顶约束:50pt
- left: 15pt
- right: 15pt
- 底部约束(同为box顶约束): Standard
- Box label
- 顶部约束(同为top label底约束): Standard
- left:15pt
- right: 15pt
- 底部约束(同为bottom label顶约束): Standard
- 高度:86pt;
- 底部label
- 居上约束(同为box底约束): Standard
- left:15pt
- right:15pt
- 距 container view 底约束: Standard
图4 添加子视图到容器视图示例
注意:如果要想 label 的 content 自动增长,就要先选中label并在属性检查器重设置 label 的行数为”0”。这样 label 的行数就没有限制了。
现在给底部的lable设置一段文字并运行这个例子,将会触发垂直滚动的条件。图5 显示了堆叠视图。从图5中可以看出,在容器视图顶部图4所示的单个视图,容器视图堆放在 scrollview 上,然后是 main view,最后是 window。
图5 模拟堆叠视图
设置垂直滚动视图编程
ok,这个部分将希望巩固最后一节概念,创建一个single-view appliction的项目并打开ViewController.h文件。
注意:如果你熟练使用 Xcode 你也可以在原来的 storybord 上拖进一个新的 ViewController,创建一个类,设置这个ViewController的类为你新创建的那个类。你可以在右侧的Identity Inspector里设置.然后把这些类目放入一标题栏里。
在ViewController.h里,需要创建以下属性:
- “contentView”命名的UIView
- “scrollView”命名的UIScrollView
- 2个UILabel ”topLabel”和”bottomLabel”
- “boxView”命名的UIView
当你完成你的代码后,你的ViewController.h文件,应该和代码1一样。
代码1 设置属性
1 2 3 4 5 6 7 8 9 10 11 |
@interface ViewControllerTwo : UIViewController @property (strong, nonatomic) UIView *contentView; @property (strong, nonatomic) UIScrollView *scrollView; @property (strong, nonatomic) UILabel *topLabel; @property (strong, nonatomic) UILabel *bottomLabel; @property (strong, nonatomic) UIView *boxView; @end |
在 ViewController.m 里,第一步需要在 viewDidload 中需要设置scrollView的宽度。如代码2中所示的添加 scrollView 和 contentView。
代码2:在主视图中添加滚动视图和内容视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- (void)viewDidLoad{ [super viewDidLoad]; self.scrollView = [[UIScrollView alloc] init]; self.scrollView.translatesAutoresizingMaskIntoConstraints = NO; self.scrollView.backgroundColor = [UIColor blueColor]; [self.view addSubview:self.scrollView]; self.contentView = [[UIView alloc] init]; self.contentView.backgroundColor = [UIColor redColor]; self.contentView.translatesAutoresizingMaskIntoConstraints = NO; [self.scrollView addSubview:self.contentView]; //.... |
再一次我们设置 scrollview 相对于 view 的 margins 为0 ,contentView 相对于 content viewmarigins 为0。最后将 content view 的 width 与 main view 的 width 设为一致。这些都是以编程方式添加的约束。将代码3中的代码添加到 viewDidLoad 方法里代码2的后面。
代码3:添加contentView和scrolView的约束
1 2 3 4 5 6 7 8 9 10 |
NSDictionary *tmpViewsDictionary = @{@"scrollView":self.scrollView, @"contentView":self.contentView}; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[scrollView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[scrollView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; |
在这份代码中,我们使用了两种不同类型的约束。第一种使用的是 Visual format language,这是一种很好的添加多个约束的字符串描述。例如:
- |意思是superview
- .意思是view之间的空间(单个 – 意味着 standard)例如.-(0)- vs. –
- (某个值value)意思是宽
- [某个view]意思是一个view
把它们连接在一起作为第一个约束 @“V:|-(0)-[scrollView]-(0)-|”
这行代码的意思是给scrollview添加距 superview 左右都为0的 margin。
更多关于visual format languge 请查看文档:Visual Format Language
宽度约束是将约束添加到视图的标准方式,你可以看得更清楚,但是有点繁琐。
最后,调用一个新的方法,把剩余的视图添加到content view中,我们把它叫做 addContentSubViews。
代码4 展示了这个复杂的 viewDidLoad 方法。
代码4 完整的 viewDidLoad 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
- (void)viewDidLoad{ [super viewDidLoad]; self.scrollView = [[UIScrollView alloc] init]; self.scrollView.translatesAutoresizingMaskIntoConstraints = NO; self.scrollView.backgroundColor = [UIColor blueColor]; [self.view addSubview:self.scrollView]; self.contentView = [[UIView alloc] init]; self.contentView.backgroundColor = [UIColor redColor]; self.contentView.translatesAutoresizingMaskIntoConstraints = NO; [self.scrollView addSubview:self.contentView]; //Auto Layout Constraints for scrolling content view NSDictionary *tmpViewsDictionary = @{@"scrollView":self.scrollView, @"contentView":self.contentView}; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[scrollView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[scrollView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(0)-[contentView]-(0)-|" options:0 metrics:nil views:tmpViewsDictionary]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; [self addContentSubViews]; } |