UIStackView内嵌UIScrollView高度为0不显示问题排查
问题成因
垂直布局的UIStackView计算arrangedSubview排布尺寸时,仅依赖子视图的固有内容尺寸或显式约束。将原水平子栈包裹进UIScrollView后出现高度为0的问题,核心原因有三点:
UIScrollView本身无固有内容尺寸,其contentSize由内部子视图约束决定,但不会将内部内容的高度反向传递给上层父视图,外层垂直栈无法自动推算UIScrollView的占据高度,默认分配高度为0。- 代码存在约束遗漏:仅给最外层
containerStack关闭了translatesAutoresizingMaskIntoConstraints,scrollView和内部scrolledStack未关闭该属性,手写约束会与系统自动生成的约束冲突。 - 未给
scrolledStack设置axis = .horizontal,与原本的水平排布需求不符;且原约束将scrolledStack宽度绑定为和scrollView相等,即便视图正常显示也无法实现水平滚动效果。
修复方案
按照以下步骤调整代码即可正常显示并实现水平滚动:
- 给
scrollView、scrolledStack添加translatesAutoresizingMaskIntoConstraints = false,保证手写约束生效。 - 给
scrolledStack设置水平排布属性:scrolledStack.axis = .horizontal,匹配原本的布局逻辑。 - 替换UIScrollView内部约束的绑定对象:滚动内容边距绑定
scrollView.contentLayoutGuide,内部栈的高度绑定scrollView.frameLayoutGuide,去掉原代码中scrolledStack与scrollView等宽的约束,让内容宽度自适应撑开,实现水平滚动。 - 给
UIScrollView添加明确的高度约束,告知外层垂直栈它的排布尺寸,可根据业务需求选择固定高度、与其他子视图等高、按比例适配容器高度任意一种规则。 - (可选调整)如果希望上下两个子视图均分垂直容器的高度,可将
containerStack.distribution修改为.fillEqually,无需额外加高度约束即可实现上下各占一半的效果。
修正后的核心代码参考:
func initViews(){ let containerStack = UIStackView() addSubview(containerStack) containerStack.axis = .vertical // 若要上下子视图均分高度可改为 .fillEqually containerStack.distribution = .equalCentering containerStack.isLayoutMarginsRelativeArrangement = true containerStack.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) containerStack.translatesAutoresizingMaskIntoConstraints = false let scrollView = UIScrollView() // 关闭自动约束转换 scrollView.translatesAutoresizingMaskIntoConstraints = false scrollView.showsHorizontalScrollIndicator = true let scrolledStack = UIStackView() // 关闭自动约束转换 scrolledStack.translatesAutoresizingMaskIntoConstraints = false // 设置水平排布 scrolledStack.axis = .horizontal containerStack.addArrangedSubview(scrollView) scrollView.addSubview(scrolledStack) scrolledStack.distribution = .fill scrolledStack.isLayoutMarginsRelativeArrangement = true scrolledStack.layoutMargins = UIEdgeInsets(top: 20, left: 30, bottom: 20, right: 30) // 绑定滚动内容边距到contentLayoutGuide scrolledStack.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor).isActive = true scrolledStack.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor).isActive = true scrolledStack.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor).isActive = true scrolledStack.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor).isActive = true // 内部栈高度与scrollView可视高度一致,保证垂直方向不滚动 scrolledStack.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor).isActive = true // 给scrollView添加高度约束,这里以固定高度200为例,可替换为其他高度规则 scrollView.heightAnchor.constraint(equalToConstant: 200).isActive = true let otherStack = UIStackView() otherStack.translatesAutoresizingMaskIntoConstraints = false otherStack.axis = .horizontal containerStack.addArrangedSubview(otherStack) otherStack.distribution = .equalCentering otherStack.isLayoutMarginsRelativeArrangement = true otherStack.layoutMargins = UIEdgeInsets(top: 20, left: 30, bottom: 0, right: 30) containerStack.topAnchor.constraint(equalTo: topAnchor).isActive = true containerStack.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true containerStack.leftAnchor.constraint(equalTo: leftAnchor).isActive = true containerStack.rightAnchor.constraint(equalTo: rightAnchor).isActive = true for number in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]{ let text = UITextView() text.font = UIFont.boldSystemFont(ofSize: 14) text.text = number text.isScrollEnabled = false text.backgroundColor = .clear scrolledStack.addArrangedSubview(text) } for number in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]{ let text = UITextView() text.font = UIFont.boldSystemFont(ofSize: 14) text.text = number text.isScrollEnabled = false text.backgroundColor = .clear otherStack.addArrangedSubview(text) } }
注意:UIScrollView 从iOS 11开始引入
contentLayoutGuide和frameLayoutGuide,约束内部滚动内容时优先绑定contentLayoutGuide,控制内部内容和滚动视图自身尺寸关系时绑定frameLayoutGuide,可以避免很多滚动区域计算错误的问题。
内容的提问来源于stack exchange,提问作者Josemafuen
相关产品推荐
相关产品推荐

