iOS UIScrollView与UIStackView布局时footer被拉伸问题求解
问题
我希望视图布局时,作为页脚的UIStackView能够紧贴自身内容,且布局优先级高于UIScrollView。当前作为页眉的UIStackView和主体UIScrollView均紧贴自身内容,导致页脚UIStackView被拉伸,内容下方出现大量留白,无法呈现固定在底部的视觉效果。我的目标是实现页眉(UIStackView)和页脚(UIStackView)紧贴自身内容,主体区域(UIScrollView)按需自适应拉伸的布局效果。
运行平台规格
- iOS 15
- Xcode 13.1
实现背景
使用的UIViewController视图层级结构如下:
UIViewController -UIView -UIStackView(header) -ScrollView(scrollable main body) -UIView -UIStackView -UIStackView(footer)
页眉和页脚的实现要求:
- 始终固定在屏幕上,不随滚动内容移动
当前已添加的约束代码
self.view.addSubview(self.headerStackView) NSLayoutConstraint.activate([ self.headerStackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.headerStackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), self.headerStackView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor) ]) self.view.addSubview(self.scrollView) NSLayoutConstraint.activate([ self.scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), self.scrollView.topAnchor.constraint(equalTo: self.headerStackView.bottomAnchor) ]) self.view.addSubview(self.footerStackView) NSLayoutConstraint.activate([ self.footerStackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), self.footerStackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), self.footerStackView.topAnchor.constraint(equalTo: self.scrollView.bottomAnchor), self.footerStackView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor) ])
解决方案
问题根源
当前垂直方向约束形成了安全区顶部 -> header -> scrollView -> footer -> 安全区底部的完整闭合链,会强制撑满整个安全区的垂直高度。当三个元素的固有内容高度总和小于安全区高度时,Auto Layout会自动拉伸阻力优先级最低的元素填补空隙。由于没有手动调整优先级,系统默认的优先级设置会导致footer被意外拉伸。
修正步骤
1. 调整内容优先级
给header、footer设置更高的垂直方向内容拥抱和抗压缩优先级,强制二者始终贴合自身内容尺寸,把所有拉伸需求分配给中间的scrollView:
// 页眉、页脚保持固有内容高度,不被拉伸/压缩 headerStackView.setContentHuggingPriority(.required, for: .vertical) headerStackView.setContentCompressionResistancePriority(.required, for: .vertical) footerStackView.setContentHuggingPriority(.required, for: .vertical) footerStackView.setContentCompressionResistancePriority(.required, for: .vertical) // scrollView 允许被拉伸,优先级低于页眉页脚 scrollView.setContentHuggingPriority(.defaultLow, for: .vertical)
2. 补全ScrollView内部约束
现有代码只设置了scrollView相对于父视图的位置约束,缺少内部内容的约束,会导致滚动区域计算异常,需要补充:
// 假设scrollView内的直接子容器view命名为contentView NSLayoutConstraint.activate([ contentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor), contentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), contentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), // 横向固定宽度,禁止横向滚动 contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor) ]) // contentView内部的stackView四边对齐contentView即可,系统会自动计算滚动内容高度
优化方案(更简便不易出错)
可以直接用一个垂直方向的根UIStackView包裹header、scrollView、footer三个元素,省去手动写三个元素上下衔接的约束:
- 根StackView设置
axis = .vertical、alignment = .fill、distribution = .fill、spacing = 0 - 根StackView四边约束到根视图的安全区域
- 按照上面步骤1给header、footer、scrollView设置对应的内容优先级即可,自动实现中间scrollView自适应拉伸、上下固定贴合内容的效果。
内容的提问来源于stack exchange,提问作者Ethan D'Mello
相关产品推荐
相关产品推荐

