Swift4实现ScrollView中适配动态高度的多个TableView
当然可以实现!这种独立TableView的方案反而比合并到单个TableView更清晰,维护起来也更省心,完全适配你iOS 11.3 + Swift 4 + Xcode 9.3的技术栈。下面是具体的实现思路和代码示例:
核心思路
我们会用「UIScrollView + 垂直UIStackView」作为容器,把4个独立的UITableView放进StackView里。关键是让每个TableView禁用自身滚动,并根据内容动态调整高度,最后由外层ScrollView负责整体滚动。
步骤1:布局容器设置
首先在Storyboard或代码里搭建基础布局:
- 给ViewController添加一个
UIScrollView,让它的leading/trailing/top/bottom和父视图完全对齐。 - 在ScrollView内部添加一个垂直排列的
UIStackView,设置它的leading/trailing/top/bottom和ScrollView的contentLayoutGuide对齐,同时让StackView的width和ScrollView的frameLayoutGuide对齐(保证TableView宽度和屏幕一致)。
步骤2:配置每个独立的TableView
每个TableView都需要做以下配置,才能实现动态高度:
- 禁用自身滚动:
tableView.isScrollEnabled = false(把滚动权交给外层ScrollView) - 开启自动行高:
tableView.rowHeight = UITableView.automaticDimension,同时设置estimatedRowHeight(比如44,给系统一个初始参考值) - 给每个TableView添加高度约束,后续动态更新这个约束的
constant值来匹配内容高度
代码示例:初始化TableView
import UIKit class MultiTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var verticalStackView: UIStackView! // 四个独立的TableView private var tableView1: UITableView! private var tableView2: UITableView! private var tableView3: UITableView! private var tableView4: UITableView! // 对应每个TableView的高度约束 private var tv1HeightConstraint: NSLayoutConstraint! private var tv2HeightConstraint: NSLayoutConstraint! private var tv3HeightConstraint: NSLayoutConstraint! private var tv4HeightConstraint: NSLayoutConstraint! // 模拟四个动态数据源 private var dataSource1: [String] = [] private var dataSource2: [String] = [] private var dataSource3: [String] = [] private var dataSource4: [String] = [] override func viewDidLoad() { super.viewDidLoad() setupAllTableViews() loadDynamicData() } private func setupAllTableViews() { // 复用方法初始化每个TableView setupSingleTableView(&tableView1, heightConstraint: &tv1HeightConstraint) setupSingleTableView(&tableView2, heightConstraint: &tv2HeightConstraint) setupSingleTableView(&tableView3, heightConstraint: &tv3HeightConstraint) setupSingleTableView(&tableView4, heightConstraint: &tv4HeightConstraint) // 将TableView添加到StackView [tableView1, tableView2, tableView3, tableView4].forEach { verticalStackView.addArrangedSubview($0) } } private func setupSingleTableView(_ tableView: inout UITableView?, heightConstraint: inout NSLayoutConstraint?) { tableView = UITableView(frame: .zero, style: .plain) tableView?.translatesAutoresizingMaskIntoConstraints = false tableView?.isScrollEnabled = false tableView?.rowHeight = UITableView.automaticDimension tableView?.estimatedRowHeight = 44 tableView?.delegate = self tableView?.dataSource = self // 注册自定义Cell(这里替换成你自己的Cell类和标识符) tableView?.register(UITableViewCell.self, forCellReuseIdentifier: "DefaultCell") // 添加高度约束 heightConstraint = tableView?.heightAnchor.constraint(equalToConstant: 0) heightConstraint?.isActive = true } }
步骤3:动态更新TableView高度
当数据源变化时,我们需要刷新TableView并更新它的高度约束,让StackView和外层ScrollView自动适配新的内容:
代码示例:更新数据与高度
extension MultiTableViewController { // 模拟加载动态数据 private func loadDynamicData() { // 这里替换成你实际的数据源获取逻辑,比如网络请求后更新数组 dataSource1 = Array(repeating: "TableView1 行数据", count: Int.random(in: 4...10)) dataSource2 = Array(repeating: "TableView2 行数据", count: Int.random(in: 2...8)) dataSource3 = Array(repeating: "TableView3 行数据", count: Int.random(in: 5...12)) dataSource4 = Array(repeating: "TableView4 行数据", count: Int.random(in: 3...9)) // 更新所有TableView的高度 updateAllTableViewHeights() } private func updateAllTableViewHeights() { // 先刷新所有TableView tableView1.reloadData() tableView2.reloadData() tableView3.reloadData() tableView4.reloadData() // 强制布局,确保contentSize正确计算 view.layoutIfNeeded() // 更新每个TableView的高度约束 tv1HeightConstraint.constant = tableView1.contentSize.height tv2HeightConstraint.constant = tableView2.contentSize.height tv3HeightConstraint.constant = tableView3.contentSize.height tv4HeightConstraint.constant = tableView4.contentSize.height // 再次布局,让ScrollView更新内容尺寸 view.layoutIfNeeded() } // MARK: - UITableView DataSource & Delegate func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch tableView { case tableView1: return dataSource1.count case tableView2: return dataSource2.count case tableView3: return dataSource3.count case tableView4: return dataSource4.count default: return 0 } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultCell", for: indexPath) switch tableView { case tableView1: cell.textLabel?.text = dataSource1[indexPath.row] case tableView2: cell.textLabel?.text = dataSource2[indexPath.row] case tableView3: cell.textLabel?.text = dataSource3[indexPath.row] case tableView4: cell.textLabel?.text = dataSource4[indexPath.row] default: break } return cell } }
额外优化建议
- 分离代理逻辑:如果四个TableView的业务逻辑差异较大,建议给每个TableView单独设置代理对象(比如创建四个小的ViewModel或Delegate类),避免在同一个ViewController里写大量
switch判断,代码更易维护。 - 监听ContentSize变化:如果担心数据更新后
contentSize没有及时更新,可以用KVO监听每个TableView的contentSize属性,自动触发高度更新(代码里可以添加KVO监听逻辑,在observeValue方法里更新对应约束)。 - Cell约束完整性:如果你的Cell是动态高度(比如包含多行文本),一定要确保Cell内部的约束是完整的(顶部、底部、左右都有明确约束),这样
UITableView.automaticDimension才能正确计算行高。
内容的提问来源于stack exchange,提问作者gabicuesta
相关产品推荐
相关产品推荐

