UICollectionView实现带自适应高度UITextView的可扩展单元格
问题核心原因
你当前的垂直方向约束链断裂,同时缺少必要的布局刷新逻辑:内嵌容器UIView没有和内部UITextView绑定底部约束,Auto Layout无法通过textView的内容高度反推容器、单元格的最终高度,UICollectionView拿不到正确的内容尺寸,自然无法同步更新contentSize触发正常滚动。另外你单元格的约束代码写错了约束对象,还漏了UITextView的关键属性配置。
分步实现方案
- 第一步:修正UITextView基础配置
首先关闭UITextView的滚动,这是让textView通过自身内容输出固有高度的核心前提,开着滚动的话textView内容超出后会在内部滚动,不会向外传递高度变化:private var textView: UITextView = { let textView = UITextView() textView.translatesAutoresizingMaskIntoConstraints = false textView.isUserInteractionEnabled = true textView.isEditable = true textView.isScrollEnabled = false // 新增核心配置 return textView }() - 第二步:补全内嵌容器视图的约束,删除冗余固定宽度
你之前给textView写的312固定宽度是多余的,textView左右已经贴合容器父视图,宽度会由父视图自动约束。重点补上textView和容器视图的底部约束,打通垂直方向的约束链路:private func setConstraints() { textView.topAnchor.constraint(equalTo: topAnchor).isActive = true textView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true textView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true // 新增底部约束,让容器高度和textView内容高度绑定 textView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true } - 第三步:修正单元格的约束逻辑
你之前单元格的约束代码直接约束textView是错误的,textView属于内嵌容器视图,单元格需要约束的是容器视图,且所有子视图必须添加在cell的contentView上,不要直接加在cell本体上,否则会出现约束计算偏差:// 先把容器视图添加到contentView contentView.addSubview(yourContainerView) yourContainerView.translatesAutoresizingMaskIntoConstraints = false func setConstraints() { yourContainerView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true yourContainerView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true yourContainerView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true yourContainerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true } - 第四步:配置UICollectionView开启自动尺寸
给collectionView的layout设置估算尺寸,让系统自动计算单元格高度,不要写死itemSize:let flowLayout = UICollectionViewFlowLayout() // 给一个接近实际值的估算尺寸即可,能提升布局计算效率 flowLayout.estimatedItemSize = CGSize(width: 312, height: 50) yourCollectionView.collectionViewLayout = flowLayout - 第五步:监听文本输入,触发布局刷新
UITextView输入内容时不会自动通知collectionView更新布局,需要在textView的代理方法中触发布局更新,同时处理滚动位置避免输入时光标跳走:func textViewDidChange(_ textView: UITextView) { let currentOffset = yourCollectionView.contentOffset UIView.performWithoutAnimation { yourCollectionView.performBatchUpdates(nil) { _ in // 布局更新后还原滚动位置,避免输入时页面跳动 yourCollectionView.setContentOffset(currentOffset, animated: false) } } }
避坑注意
- 不要给textView、容器视图、cell的contentView添加任何固定高度约束,否则会覆盖自适应计算的高度
- 不要手动计算textView的高度赋值给约束,Auto Layout会根据textView的字体、边距、内容自动计算准确高度,手动计算容易因为边距、系统版本差异出问题
- 如果输入超长文本时出现卡顿,可以给textView单独添加高度约束,在
textViewDidChange里计算内容高度更新约束值,比全量刷新布局性能更好,常规输入场景下用上面的批量更新方案足够。
内容的提问来源于stack exchange,提问作者Evgeniy Kleban
相关产品推荐
相关产品推荐

