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

如何调整包含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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 21:05:55