使用 栈视图(Stack View) 能节省很多重复的自动布局代码,但是有时我们更希望它能表现得像表视图可以滚动内容。UIStackView 类不是 UIScrollView 的子类,但并不能阻止我们将栈视图/嵌入到滚动视图中。
何时使用
很多时候,/你可能考虑在滚动视图中使用栈视图。两种常见情况是:
- 当键盘出现时,你希望移动栈视图的内容。
- 栈视图需要采用不同的尺寸分类(size classes)。比如,填充 regular 尺寸的视图的栈视图可能也在 compact 尺寸的视图上显示。
另一方面,当表视图更合适时,请不要使用栈视图。
创建步骤
考虑到例子中的垂直栈视图包含了多个图片,当图像被添加到栈视图时,我希望它们在屏幕顶部的标签和底部的标签栏之前滚动(保持标签可见)
这个视图拥有多个栈视图。如下图中的故事板:
- 内容栈视图(Content Stack View):这个垂直的栈视图包含标签和滚动视图。约束(未显示)将其钉在根视图的边缘。它使用填充对齐和填充分布来拉伸子视图以填充可用空间。
- 标签栈视图(Label Stack View):这个垂直的栈视图包含三个
UILabel 对象。它使用居中对齐和填充分布。
滚动视图(Scroll View):这个滚动视图填充内容视图的其他空间,并包含图片栈视图。
图片栈视图(Image Stack View):图片栈视图包含我们的图片对象并作为滚动视图的内容。
图片栈视图必须和栈视图边缘有前后左右的约束。滚动视图与栈视图等宽,其宽度由滚动视图决定。
可以参考下图的界面构建器的视图和约束:
代码实现:
1 2 3 4 5 |
stackView.leadingAnchor.constraintEqualToAnchor(scrollView.leadingAnchor).active = true stackView.trailingAnchor.constraintEqualToAnchor(scrollView.trailingAnchor).active = true stackView.bottomAnchor.constraintEqualToAnchor(scrollView.bottomAnchor).active = true stackView.topAnchor.constraintEqualToAnchor(scrollView.topAnchor).active = true stackView.widthAnchor.constraintEqualToAnchor(scrollView.widthAnchor).active = true |
点击手势
为了演示当我们在栈视图中添加和删除视图,我添加了三个手势,其中每一个都在视图控制器中有相应的动作方法:
- 单指点击:添加一个心型图像
- 双指点击:添加一个星星形象
- 三手指点击:清除图像栈视图
在栈视图中添加视图
函数响应单指点击添加心形图像到栈视图,然后滚动,使添加的图像可见:
1 2 3 4 5 6 |
@IBAction func singleTap(sender: UITapGestureRecognizer) { let heartImage = UIImage(named: "Heart") let heartImageView = UIImageView(image: heartImage) stackView.addArrangedSubview(heartImageView) scrollToEnd(heartImageView) } |
双指点击的方法是相似的,所以此处略过。下面是三指清空栈视图的方法:
1 2 3 4 5 6 7 |
@IBAction func threeFingerTap(sender: UITapGestureRecognizer) { let views = stackView.arrangedSubviews for entry in views { stackView.removeArrangedSubview(entry) entry.removeFromSuperview() } } |
滚动
滚动到栈视图的底部有点棘手。在我们向栈视图添加视图时,系统尚未完成布局传递。这意味着它尚未重新计算栈视图的 bounds,因此滚动视图的内容大小也没有变化。
因为我们知道我们添加的视图的大小,我们可以计算出滚动视图的新的内容偏移量。这是我最后的实现:
1 2 3 4 5 6 7 |
private func scrollToEnd(addedView: UIView) { let contentViewHeight = scrollView.contentSize.height + addedView.bounds.height + stackView.spacing let offsetY = contentViewHeight - scrollView.bounds.height if (offsetY > 0) { scrollView.setContentOffset(CGPoint(x: scrollView.contentOffset.x, y: offsetY), animated: true) } } |
当一切都设置好了后,我们就能在栈视图增长超出了滚动视图的可视范围时滚动它:
实例代码
你可以从我的 Github 代码示例仓库中找到 Stacks Xcode 项目。