如何调整包含UITextView的UICollectionViewListCell尺寸?
解决UICollectionView单元格随UITextView内容自动调整高度的问题
这是一个使用组合布局和差异数据源的简单UICollectionView,单元格类型为UICollectionViewListCell,其contentView中嵌套了一个UITextView作为子视图。我希望单元格能随着UITextView的内容变化自动调整高度,但尝试在textViewDidChange(_:)中调用collectionView.setNeedsLayout(),以及将textView的isScrollEnabled设为false都没有效果。之前处理UITableView时,通过在textViewDidChange(_:)中调用tableView.beginUpdates(); tableView.endUpdates()实现了单元格尺寸调整,现在想知道UICollectionView下的解决方案。
原ViewController代码
import UIKit class ViewController: UIViewController { var collectionView: UICollectionView! let textView = UITextView() var dataSource: UICollectionViewDiffableDataSource<Section, Int>! enum Section: CaseIterable { case first } override func viewDidLoad() { super.viewDidLoad() configureHierarchy() configureDataSource() } private func configureHierarchy() { collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout()) view.addSubview(collectionView) collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth] textView.delegate = self } func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Int> { [weak self] cell, indexPath, itemIdentifier in guard let self else { return } cell.contentView.addSubview(textView) textView.pinToSuperviewMargins() } dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier) } var snapshot = NSDiffableDataSourceSnapshot<Section, Int>() snapshot.appendSections(Section.allCases) snapshot.appendItems([1], toSection: .first) dataSource.apply(snapshot) } func createLayout() -> UICollectionViewLayout { UICollectionViewCompositionalLayout { section, layoutEnvironment in var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment) } } } extension ViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { // Do something here? } }
辅助布局扩展代码
extension UIView { func pinToSuperviewMargins( top: CGFloat = 0, bottom: CGFloat = 0, leading: CGFloat = 0, trailing: CGFloat = 0, file: StaticString = #file, line: UInt = #line ) { guard let superview = self.superview else { let localFilePath = URL(fileURLWithPath: "\(file)").lastPathComponent print(">>> \(#function) failed in file: \(localFilePath), at line: \(line): could not find \(Self.self).superView.") return } self.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.topAnchor.constraint(equalTo: superview.topAnchor, constant: top), self.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: bottom), self.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: leading), self.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant: trailing), ]) } func pinToSuperviewMargins(constant c: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { self.pinToSuperviewMargins(top: c, bottom: -c, leading: c, trailing: -c, file: file, line: line) } }
解决方案
1. 修复TextView复用冲突
原代码将textView作为ViewController全局属性,会导致同一个TextView被重复添加到不同单元格,引发布局混乱。需改为每个单元格创建独立的TextView:
func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Int> { [weak self] cell, indexPath, itemIdentifier in guard let self else { return } let textView = UITextView() textView.delegate = self textView.isScrollEnabled = false // 必须禁用滚动,让TextView自动计算高度 cell.contentView.addSubview(textView) textView.pinToSuperviewMargins() } // 其余代码保持不变 }
2. 触发CollectionView重新计算高度
在textViewDidChange(_:)中,通过重新加载对应item的快照或执行批量更新,让CollectionView重新计算单元格高度:
extension ViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { // 获取当前TextView所在的单元格及索引 guard let cell = textView.superview?.superview as? UICollectionViewListCell, let indexPath = collectionView.indexPath(for: cell) else { return } // 方案1:重新应用快照(适配DiffableDataSource) var snapshot = dataSource.snapshot() snapshot.reloadItems([indexPath.item]) dataSource.apply(snapshot, animatingDifferences: false) // 方案2:执行批量更新,类似TableView的begin/endUpdates // collectionView.performBatchUpdates(nil, completion: nil) } }
3. 优化布局预估配置
在创建组合布局时,可设置预估高度提升滚动性能:
func createLayout() -> UICollectionViewLayout { UICollectionViewCompositionalLayout { section, layoutEnvironment in var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) config.estimatedItemHeight = 100 // 设置预估高度 return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment) } }
关键注意点
- 必须禁用TextView的
isScrollEnabled,否则TextView会固定高度,无法自动扩展。 - 每个单元格需持有独立的TextView,避免复用冲突。
- 触发布局更新时,两种方案任选其一,都能让CollectionView重新计算单元格高度。
内容的提问来源于stack exchange,提问作者Filippo
相关产品推荐
相关产品推荐

