连续快速调用scrollToItem导致UICollectionView滚动异常求助
解决方案
一、解决快速点击时scrollToItem动画卡顿问题
方案1:终止未完成动画,仅执行最新滚动请求
每次点击芯片时,先强制终止当前未完成的滚动动画,再执行新的滚动操作,避免多个动画叠加导致卡顿。
private var isScrolling = false func chipTapped(at indexPath: IndexPath) { // 1. 立即终止当前所有滚动动画 chipCollectionView.setContentOffset(chipCollectionView.contentOffset, animated: false) // 2. 更新选中状态,刷新前后两个选中Cell let prevIndex = selectedChipIndexPath selectedChipIndexPath = indexPath var reloadPaths = [IndexPath]() prevIndex.map { reloadPaths.append($0) } reloadPaths.append(indexPath) chipCollectionView.reloadItems(at: reloadPaths) // 3. 执行平滑滚动到中心 isScrolling = true UIView.animate(withDuration: 0.3, options: .curveEaseInOut) { self.chipCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false) } completion: { _ in self.isScrolling = false } }
方案2:合并短时间内的重复滚动请求
通过串行队列+延迟执行,忽略快速连续点击中的中间请求,仅处理最后一次点击的滚动操作。
private let scrollQueue = DispatchQueue(label: "com.app.chip.scroll", attributes: .concurrent) private var pendingScrollTask: DispatchWorkItem? func chipTapped(at indexPath: IndexPath) { // 1. 取消之前未执行的滚动任务 pendingScrollTask?.cancel() // 2. 更新选中状态 let prevIndex = selectedChipIndexPath selectedChipIndexPath = indexPath var reloadPaths = [IndexPath]() prevIndex.map { reloadPaths.append($0) } reloadPaths.append(indexPath) chipCollectionView.reloadItems(at: reloadPaths) // 3. 创建新滚动任务,延迟0.1秒执行(可根据需求调整延迟时长) let task = DispatchWorkItem { [weak self] in guard let self = self, !self.chipCollectionView.isDragging else { return } DispatchQueue.main.async { self.chipCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) } } pendingScrollTask = task scrollQueue.asyncAfter(deadline: .now() + 0.1, execute: task) }
二、解决选中时Label变粗体导致的尺寸/截断问题
方案1:提前计算字体尺寸,设置Cell最小宽度
在Cell配置阶段,提前计算常规/粗体字体下的文本宽度,将Cell最小宽度设为较大值,避免切换字体时尺寸突变。
class ChipCell: UICollectionViewCell { let titleLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) setupLayout() } required init?(coder: NSCoder) { super.init(coder: coder) setupLayout() } private func setupLayout() { titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.textAlignment = .center contentView.addSubview(titleLabel) NSLayoutConstraint.activate([ titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16), titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16), titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8), titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8) ]) } func configure(title: String, isSelected: Bool) { titleLabel.text = title // 计算两种字体的文本宽度 let regularFont = UIFont.systemFont(ofSize: 14) let boldFont = UIFont.boldSystemFont(ofSize: 14) let regularWidth = title.size(withAttributes: [.font: regularFont]).width let boldWidth = title.size(withAttributes: [.font: boldFont]).width // 设置Cell最小宽度为较大值+内边距 let minWidth = max(regularWidth, boldWidth) + 32 contentView.widthAnchor.constraint(greaterThanOrEqualToConstant: minWidth).isActive = true // 切换字体与选中样式 titleLabel.font = isSelected ? boldFont : regularFont contentView.backgroundColor = isSelected ? .systemBlue : .systemGray5 } }
方案2:依赖自动布局自适应尺寸
给Label设置最高内容优先级,同时让CollectionView的FlowLayout启用自动尺寸计算,确保Cell能根据字体自动调整宽度。
// 在ChipCell的setupLayout方法中添加: titleLabel.setContentHuggingPriority(.required, for: .horizontal) titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal) // 初始化CollectionView时配置FlowLayout: let flowLayout = UICollectionViewFlowLayout() flowLayout.scrollDirection = .horizontal flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize chipCollectionView.collectionViewLayout = flowLayout
三、组合优化建议
- 优先采用终止未完成动画的方案解决卡顿,响应更及时;
- 配合提前计算Cell最小宽度的方案处理字体切换问题,避免因刷新布局引发的额外卡顿;
- 可通过
isScrolling变量控制,避免在滚动动画执行期间触发Cell刷新。
内容的提问来源于stack exchange,提问作者Nikhil Muskur
相关产品推荐
相关产品推荐

