AutoLayout 实现固定宽度动态高度的 ScrollView

575 查看

作为一个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 设置属性

 

在 ViewController.m 里,第一步需要在 viewDidload 中需要设置scrollView的宽度。如代码2中所示的添加 scrollView 和 contentView。

代码2:在主视图中添加滚动视图和内容视图

 

再一次我们设置 scrollview 相对于 view 的 margins 为0 ,contentView 相对于 content viewmarigins 为0。最后将 content view 的 width 与 main view 的 width 设为一致。这些都是以编程方式添加的约束。将代码3中的代码添加到 viewDidLoad 方法里代码2的后面。

代码3:添加contentView和scrolView的约束

 

在这份代码中,我们使用了两种不同类型的约束。第一种使用的是 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];
}

 

addContentSubViews方法十分简单,仅仅创建了几个label和一个box view.label的numberoflines = 0,意味着lable的content会满足多行的需求。label 居中且自动换行。

代码5 展示了应该添加下面的viewDidLoad方法完整的方法。

代码5: addContentSubViews 实现