You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS带固定分区表头、可展开折叠子TableView的TableViewController实现求助

实现带固定表头、可展开折叠的嵌套TableView

我来帮你实现这个需求,下面是完整的思路和示例代码,涵盖固定表头、父单元格展开折叠、子TableView嵌套及跳转逻辑:

一、数据模型设计

首先定义清晰的数据结构,用来存储表头、父单元格状态和子单元格数据:

// 子单元格数据模型(存储跳转所需信息)
struct SubCellModel {
    let title: String
    let targetPageTag: String? // 可自定义跳转标识或参数
}

// 父单元格数据模型(控制展开折叠状态)
struct ParentCellModel {
    let title: String
    var isExpanded: Bool
    let subCells: [SubCellModel]
}

// 表头分组模型
struct HeaderSectionModel {
    let headerTitle: String
    var parentCells: [ParentCellModel]
}

二、主TableViewController实现

主TableView的每个Section对应一个固定表头,每个Row对应一个可展开的父单元格:

import UIKit

class MainTableViewController: UITableViewController {
    
    // 模拟数据源
    var sections: [HeaderSectionModel] = [
        HeaderSectionModel(
            headerTitle: "Header 1",
            parentCells: [
                ParentCellModel(title: "Cell 1.1", isExpanded: false, subCells: []),
                ParentCellModel(title: "Cell 1.2", isExpanded: true, subCells: [
                    SubCellModel(title: "subCell 1.2.1", targetPageTag: "Detail"),
                    SubCellModel(title: "subCell 1.2.2", targetPageTag: "Detail")
                ]),
                ParentCellModel(title: "Cell 1.3", isExpanded: false, subCells: [])
            ]
        ),
        HeaderSectionModel(
            headerTitle: "Header 2",
            parentCells: [
                ParentCellModel(title: "Cell 2.1", isExpanded: false, subCells: []),
                ParentCellModel(title: "Cell 2.2", isExpanded: false, subCells: [
                    SubCellModel(title: "subCell 2.2.1", targetPageTag: "Detail"),
                    SubCellModel(title: "subCell 2.2.2", targetPageTag: "Detail")
                ])
            ]
        )
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
    }
    
    private func setupTableView() {
        // 注册父单元格
        tableView.register(ParentExpandableCell.self, forCellReuseIdentifier: "ParentCell")
        // 固定表头高度
        tableView.sectionHeaderHeight = 50
        // 自动计算行高(适配展开/折叠状态)
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = 80
        tableView.separatorStyle = .singleLine
    }
    
    // MARK: - TableView DataSource
    override func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sections[section].parentCells.count
    }
    
    // 自定义固定表头
    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 50))
        headerView.backgroundColor = .systemGray5
        
        let titleLabel = UILabel(frame: CGRect(x: 16, y: 0, width: headerView.frame.width - 32, height: 50))
        titleLabel.text = sections[section].headerTitle
        titleLabel.font = .boldSystemFont(ofSize: 16)
        headerView.addSubview(titleLabel)
        
        return headerView
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ParentCell", for: indexPath) as! ParentExpandableCell
        
        let parentModel = sections[indexPath.section].parentCells[indexPath.row]
        cell.configure(with: parentModel)
        
        // 绑定子TableView代理
        cell.subTableView.dataSource = cell
        cell.subTableView.delegate = cell
        
        // 展开折叠回调
        cell.onExpandToggle = { [weak self] in
            self?.sections[indexPath.section].parentCells[indexPath.row].isExpanded.toggle()
            tableView.reloadRows(at: [indexPath], with: .automatic)
        }
        
        // 子单元格跳转回调
        cell.onSubCellTap = { [weak self] subModel in
            self?.navigateToTargetPage(with: subModel)
        }
        
        return cell
    }
}

// MARK: - 跳转逻辑
extension MainTableViewController {
    private func navigateToTargetPage(with subModel: SubCellModel) {
        // 示例跳转至详情页,可根据实际需求修改
        let detailVC = DetailViewController()
        detailVC.title = subModel.title
        navigationController?.pushViewController(detailVC, animated: true)
    }
}

三、可展开的父单元格(含子TableView)

自定义ParentExpandableCell,封装标题、展开按钮和子TableView的逻辑:

class ParentExpandableCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate {
    
    // UI组件
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.font = .systemFont(ofSize: 16)
        return label
    }()
    
    private let expandBtn: UIButton = {
        let btn = UIButton(type: .system)
        btn.setImage(UIImage(systemName: "chevron.right"), for: .normal)
        btn.tintColor = .darkGray
        return btn
    }()
    
    let subTableView: UITableView = {
        let tableView = UITableView()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "SubCell")
        tableView.separatorStyle = .singleLine
        // 隐藏子TableView的空表头/表尾
        tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude))
        tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude))
        return tableView
    }()
    
    // 数据模型
    private var parentModel: ParentCellModel!
    
    // 回调闭包
    var onExpandToggle: (() -> Void)?
    var onSubCellTap: ((SubCellModel) -> Void)?
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupLayout()
        setupActions()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupLayout() {
        contentView.addSubview(titleLabel)
        contentView.addSubview(expandBtn)
        contentView.addSubview(subTableView)
        
        // 禁用自动布局转换
        [titleLabel, expandBtn, subTableView].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
        
        NSLayoutConstraint.activate([
            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            
            expandBtn.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            expandBtn.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            
            subTableView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8),
            subTableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 32),
            subTableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            subTableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8)
        ])
    }
    
    private func setupActions() {
        expandBtn.addTarget(self, action: #selector(expandBtnTapped), for: .touchUpInside)
    }
    
    @objc private func expandBtnTapped() {
        onExpandToggle?()
        // 更新按钮图标
        let iconName = parentModel.isExpanded ? "chevron.down" : "chevron.right"
        expandBtn.setImage(UIImage(systemName: iconName), for: .normal)
    }
    
    // 配置单元格数据
    func configure(with model: ParentCellModel) {
        self.parentModel = model
        titleLabel.text = model.title
        
        // 设置初始图标和子TableView显示状态
        let iconName = model.isExpanded ? "chevron.down" : "chevron.right"
        expandBtn.setImage(UIImage(systemName: iconName), for: .normal)
        subTableView.isHidden = !model.isExpanded
        
        // 刷新子TableView数据
        subTableView.reloadData()
    }
    
    // MARK: - Sub TableView DataSource & Delegate
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return parentModel.subCells.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "SubCell", for: indexPath)
        let subModel = parentModel.subCells[indexPath.row]
        cell.textLabel?.text = subModel.title
        cell.textLabel?.font = .systemFont(ofSize: 14)
        cell.accessoryType = .disclosureIndicator // 显示跳转箭头
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let subModel = parentModel.subCells[indexPath.row]
        onSubCellTap?(subModel)
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 44
    }
}

四、示例详情页

简单的目标跳转页面示例:

class DetailViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        navigationItem.title = title
    }
}

核心要点说明

  • 数据驱动UI:通过isExpanded字段控制父单元格的展开状态,子数据内嵌在父模型中,逻辑更清晰。
  • 自动行高适配:主TableView启用自动行高,配合子TableView的显示/隐藏,自动调整父单元格高度。
  • 组件解耦:父单元格自行处理子TableView的代理逻辑,通过闭包与主控制器通信,避免代码臃肿。

内容的提问来源于stack exchange,提问作者Vishwanath Deshmukh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:53:17