如何实现TableView单元格高度动态切换:点击按钮收起TextView并缩小单元格
嘿Alex,刚好我之前做过类似的需求,给你详细讲讲怎么实现这个支持折叠/展开、两种高度的TableView单元格,步骤和代码都给你整理好了👇
实现TableView单元格的折叠/展开(双高度切换)
核心思路是通过维护单元格的展开状态,结合自动布局让TableView自动计算不同状态下的单元格高度,点击按钮时切换状态并刷新对应单元格即可。
第一步:定义带状态的数据源模型
首先要给每个单元格对应的数据加一个标记,记录当前是「展开」还是「收起」状态,这是控制高度变化的核心:
struct CellData { let title: String // Label显示的标题 let content: String // TextView显示的内容 var isExpanded: Bool // 标记单元格是否展开 }
(如果用Objective-C,思路完全一致,只需把结构体改成带BOOL isExpanded属性的OC类即可)
第二步:自定义可折叠的TableViewCell
创建自定义Cell,包含Label、TextView和Button三个组件,用自动布局做好约束,同时给按钮添加点击回调,用来通知控制器切换状态:
class ExpandableCell: UITableViewCell { // 初始化组件 private let titleLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 16, weight: .bold) label.translatesAutoresizingMaskIntoConstraints = false return label }() private let contentTextView: UITextView = { let tv = UITextView() tv.font = .systemFont(ofSize: 14) tv.isEditable = false tv.isScrollEnabled = false // 收起时不需要滚动 tv.translatesAutoresizingMaskIntoConstraints = false return tv }() private let toggleBtn: UIButton = { let btn = UIButton(type: .system) btn.setTitle("收起", for: .normal) btn.setTitle("展开", for: .selected) btn.translatesAutoresizingMaskIntoConstraints = false return btn }() // 按钮点击的回调闭包,用来通知控制器切换状态 var toggleHandler: (() -> Void)? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupLayout() setupBtnAction() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // 配置组件布局约束 private func setupLayout() { contentView.addSubview(titleLabel) contentView.addSubview(contentTextView) contentView.addSubview(toggleBtn) NSLayoutConstraint.activate([ titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16), titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), titleLabel.trailingAnchor.constraint(equalTo: toggleBtn.leadingAnchor, constant: -8), toggleBtn.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor), toggleBtn.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), contentTextView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12), contentTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), contentTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), contentTextView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16) ]) } // 绑定按钮点击事件 private func setupBtnAction() { toggleBtn.addTarget(self, action: #selector(toggleCellState), for: .touchUpInside) } @objc private func toggleCellState() { toggleHandler?() } // 给Cell配置数据,同时根据状态更新UI func configure(with data: CellData) { titleLabel.text = data.title contentTextView.text = data.content // 收起时隐藏TextView,展开时显示 contentTextView.isHidden = !data.isExpanded // 切换按钮的选中状态,对应显示"展开"/"收起"文字 toggleBtn.isSelected = !data.isExpanded } }
第三步:控制器层的逻辑处理
在ViewController里维护数据源,处理按钮点击后的状态切换,同时让TableView自动计算不同状态下的单元格高度:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { private let tableView = UITableView() // 初始化测试数据源 private var cellDataList: [CellData] = [ CellData(title: "标题1", content: "这里是一段较长的测试内容,展开后会完整显示,收起时TextView会隐藏,单元格高度自动缩小。", isExpanded: true), CellData(title: "标题2", content: "这是另一段测试内容,用来验证折叠展开的动画效果是否流畅自然。", isExpanded: false) ] override func viewDidLoad() { super.viewDidLoad() setupTableView() } private func setupTableView() { tableView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tableView) // 布局TableView NSLayoutConstraint.activate([ tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) ]) // 注册自定义Cell tableView.register(ExpandableCell.self, forCellReuseIdentifier: "ExpandableCell") tableView.dataSource = self tableView.delegate = self // 设置自动计算单元格高度 tableView.rowHeight = UITableView.automaticDimension // 设置预估高度,提升TableView滚动性能 tableView.estimatedRowHeight = 80 } // MARK: - UITableView数据源方法 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return cellDataList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "ExpandableCell", for: indexPath) as! ExpandableCell let data = cellDataList[indexPath.row] cell.configure(with: data) // 绑定按钮点击的回调:切换状态并刷新当前单元格 cell.toggleHandler = { [weak self] in guard let self = self else { return } self.cellDataList[indexPath.row].isExpanded.toggle() // 只刷新当前单元格,带动画效果 tableView.reloadRows(at: [indexPath], with: .automatic) } return cell } }
关键注意点
- 自动布局是核心:确保Cell内所有组件都有完整的约束,这样
UITableView.automaticDimension才能正确计算不同状态下的高度。 - 精准刷新单元格:用
reloadRows(at:with:)代替reloadData(),既能保证动画流畅,又不会影响其他单元格的状态。 - 可选优化:如果不想完全隐藏TextView,也可以通过设置
textView.numberOfLines来实现「收起时显示N行,展开时显示全部」的效果,只需修改Cell的configure方法即可。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

