Swift中如何在父ScrollView中设置子ViewController容器高度并适配滚动内容
解决方案
第一步:子VC侧的自计算高度适配
- 给每个子VC新增公开只读属性
contentHeight,直接返回自身内容总高度,你可以直接复用原ScrollView移除前记录的contentSize.height,也可以通过子视图实时计算:
// Swift示例 var contentHeight: CGFloat { // 取所有子视图的最大下边缘作为内容总高度 return view.subviews.reduce(0) { max($0, $1.frame.maxY) } }
- 确保子VC主视图没有固定高度约束,所有子控件的上下约束完整,可通过子视图撑出主视图的实际高度。
第二步:父VC侧推荐用Auto Layout实现(无需手动计算contentSize)
- 先给父VC添加统一的滚动容器,铺满父视图:
let parentScrollView = UIScrollView() parentScrollView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(parentScrollView) NSLayoutConstraint.activate([ parentScrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), parentScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), parentScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), parentScrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) ])
- 给滚动容器添加contentView,用于自动撑出滚动范围:
let contentView = UIView() contentView.translatesAutoresizingMaskIntoConstraints = false parentScrollView.addSubview(contentView) NSLayoutConstraint.activate([ // 绑定contentView和scrollView的content布局 Guide contentView.topAnchor.constraint(equalTo: parentScrollView.contentLayoutGuide.topAnchor), contentView.leadingAnchor.constraint(equalTo: parentScrollView.contentLayoutGuide.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: parentScrollView.contentLayoutGuide.trailingAnchor), contentView.bottomAnchor.constraint(equalTo: parentScrollView.contentLayoutGuide.bottomAnchor), // 固定contentView宽度和scrollView一致,仅支持垂直滚动 contentView.widthAnchor.constraint(equalTo: parentScrollView.widthAnchor) ])
- 添加两个子VC的容器视图,垂直排列,高度直接绑定子VC的内容高度:
// 容器A对应子VC A let containerA = UIView() containerA.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(containerA) addChild(vcA) vcA.view.translatesAutoresizingMaskIntoConstraints = false containerA.addSubview(vcA.view) NSLayoutConstraint.activate([ vcA.view.topAnchor.constraint(equalTo: containerA.topAnchor), vcA.view.leadingAnchor.constraint(equalTo: containerA.leadingAnchor), vcA.view.trailingAnchor.constraint(equalTo: containerA.trailingAnchor), vcA.view.bottomAnchor.constraint(equalTo: containerA.bottomAnchor) ]) vcA.didMove(toParent: self) // 容器B对应子VC B,实现逻辑同上 let containerB = UIView() containerB.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(containerB) addChild(vcB) vcB.view.translatesAutoresizingMaskIntoConstraints = false containerB.addSubview(vcB.view) NSLayoutConstraint.activate([ vcB.view.topAnchor.constraint(equalTo: containerB.topAnchor), vcB.view.leadingAnchor.constraint(equalTo: containerB.leadingAnchor), vcB.view.trailingAnchor.constraint(equalTo: containerB.trailingAnchor), vcB.view.bottomAnchor.constraint(equalTo: containerB.bottomAnchor) ]) vcB.didMove(toParent: self) // 垂直排列两个容器,绑定高度 let containerAHeightConstraint = containerA.heightAnchor.constraint(equalToConstant: vcA.contentHeight) let containerBHeightConstraint = containerB.heightAnchor.constraint(equalToConstant: vcB.contentHeight) NSLayoutConstraint.activate([ containerA.topAnchor.constraint(equalTo: contentView.topAnchor), containerA.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), containerA.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), containerAHeightConstraint, containerB.topAnchor.constraint(equalTo: containerA.bottomAnchor), containerB.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), containerB.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), containerB.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), containerBHeightConstraint ])
注意:该方案下父ScrollView的contentSize会通过contentView的约束自动计算,无需手动赋值。
第三步:动态内容更新处理
如果子VC的内容会动态变化(比如异步加载数据后更新视图),子VC内容更新后主动触发父VC更新容器高度即可:
// 子VC内容更新后调用该回调 vcA.onContentChanged = { [weak self] newHeight in guard let self = self else { return } self.containerAHeightConstraint.constant = newHeight UIView.animate(withDuration: 0.2) { self.view.layoutIfNeeded() } }
可选:Frame布局方案的处理
如果你坚持用frame布局,可按以下逻辑计算:
- 子VC布局完成后,调用
systemLayoutSizeFitting获取实际高度:
let targetSize = CGSize(width: parentScrollView.bounds.width, height: UIView.layoutFittingCompressedSize.height) let vcAHeight = vcA.view.systemLayoutSizeFitting(targetSize).height containerA.frame = CGRect(x: 0, y: 0, width: parentScrollView.bounds.width, height: vcAHeight) let vcBHeight = vcB.view.systemLayoutSizeFitting(targetSize).height containerB.frame = CGRect(x: 0, y: vcAHeight, width: parentScrollView.bounds.width, height: vcBHeight) // 手动更新父ScrollView的contentSize parentScrollView.contentSize = CGSize(width: parentScrollView.bounds.width, height: vcAHeight + vcBHeight)
- 子VC内容更新时,重新执行上述计算逻辑,更新container的frame和scrollView的contentSize即可。
内容的提问来源于stack exchange,提问作者Vinoth Kumar
相关产品推荐
相关产品推荐

