UICollectionView输入UITextView时如何避免滚动重置并定位光标位置
解决方案
核心问题出在你调用了全局的布局失效方法,会触发整个UICollectionView重新计算所有单元格布局,导致原有滚动偏移被重置。按以下步骤修改即可同时满足两个需求:
核心修改步骤
1. 替换全局布局失效为单单元格失效
不要直接调用无参数的invalidateLayout(),而是创建自定义失效上下文,仅指定需要刷新的当前单元格,避免全局布局重算:
func textViewDidChange(_ checklistCell: ChecklistCell) { // 先同步数据、获取当前单元格索引 guard let indexPath = collectionView.indexPath(for: checklistCell), let textView = checklistCell.textView else { return } let item = indexPath.item self.checklists[item].text = textView.text // 仅失效当前变化的单元格,不触动其他单元格布局缓存 let invalidContext = UICollectionViewLayoutInvalidationContext() invalidContext.invalidateItems(at: [indexPath]) collectionView.collectionViewLayout.invalidateLayout(with: invalidContext) // 布局更新后滚动到光标位置 DispatchQueue.main.async { [weak self] in guard let self = self else { return } // 计算光标在collectionView坐标系中的位置 if let cursorRange = textView.selectedTextRange { let cursorRect = textView.convert(textView.caretRect(for: cursorRange.start), to: self.collectionView) // 滚动到光标可见,添加少量上下内边距避免贴边 self.collectionView.scrollRectToVisible(cursorRect.insetBy(dx: 0, dy: -2), animated: false) } } }
2. 确认基础配置无误
提前检查以下配置,避免底层逻辑出错:
UITextView的isScrollEnabled必须设为falseUICollectionViewCompositionalLayout的组高度要设置为.estimated(44),不要用固定高度- 单元格内
UITextView的上下左右四条边必须完整约束到单元格的contentView,保证单元格高度能被文本内容撑起来
原理解释
全局调用invalidateLayout()时,UICollectionView会清空所有布局缓存,重新计算所有单元格的尺寸和位置,原有的滚动偏移是基于旧的估算高度计算的,重算后就会出现偏移跳变。仅失效单个单元格时,其他单元格的布局缓存会保留,仅重算当前单元格的高度,不会触发全局滚动偏移调整,自然就不会出现滚动位置重置的问题。
该方案和Diffable Data Source没有冲突,不需要调用performBatchUpdates或者重新应用快照,性能开销更低。
内容的提问来源于stack exchange,提问作者Cheok Yan Cheng
相关产品推荐
相关产品推荐

