RXSwift中collectionView更新数据源后indexPathsForVisibleItems为空问题
问题原因
- 你的猜测完全正确,
rx.items收到数据源更新信号后,会触发UICollectionView.reloadData(),但这个方法是异步批量操作,不会立刻完成cell的布局和渲染。你在数据源的订阅回调里立刻调用indexPathsForVisibleItems时,reload流程还没执行完,自然拿不到最新的可见cell索引。 - 额外补充:两个订阅的执行顺序没有强制保证,即使你先写了rx.items的绑定,也不代表rx.items的闭包会先于你自定义的订阅回调执行完成。
解决方案
方案1:主队列异步延迟执行(最简便,推荐)
把#2位置的判断逻辑扔到主队列的下一个RunLoop执行,此时reloadData的UI渲染已经完成,能拿到正确的可见索引,修改你的订阅代码如下:
self.viewModel.dataSource .subscribe { [weak self] images in guard let self = self, let images = images.element else { return } let lastImageIndexPath = IndexPath(row: images.count - 1, section: 0) // 放到主队列异步执行,等待reload完成 DispatchQueue.main.async { let visibleIndexPaths = self.imageCollectionView.indexPathsForVisibleItems let shouldMoveToRight = visibleIndexPaths.contains(lastImageIndexPath) if shouldMoveToRight { self.imageCollectionView.scrollToItem(at: lastImageIndexPath, at: .left, animated: true) } } } .disposed(by: self.disposeBag)
方案2:监听CollectionView刷新完成回调
如果想更严谨,避免依赖RunLoop顺序,可以通过RxCocoa监听CollectionView的布局完成事件,需要先扩展UICollectionView:
extension Reactive where Base: UICollectionView { var didFinishReloadData: Observable<Void> { Observable.create { observer in base.performBatchUpdates(nil) { _ in observer.onNext(()) } return Disposables.create() } } }
然后把数据源信号和刷新完成信号结合:
self.viewModel.dataSource .withLatestFrom(imageCollectionView.rx.didFinishReloadData) { ($0, $1) } .subscribe { [weak self] (images, _) in guard let self = self, let images = images.element else { return } let lastImageIndexPath = IndexPath(row: images.count - 1, section: 0) let visibleIndexPaths = self.imageCollectionView.indexPathsForVisibleItems let shouldMoveToRight = visibleIndexPaths.contains(lastImageIndexPath) if shouldMoveToRight { self.imageCollectionView.scrollToItem(at: lastImageIndexPath, at: .left, animated: true) } } .disposed(by: self.disposeBag)
额外优化建议
- 不要对
dataSource做重复订阅,如果你只需要判断滚动逻辑,完全可以把两个逻辑合并,减少不必要的信号订阅开销。 - 删除按钮的回调里不需要手动调用
self.imageCollectionView.layoutIfNeeded(),rx.items会自动处理数据源更新后的刷新逻辑,多余调用反而可能造成重复布局。 - 避免使用强制解包
self!,改用guard let安全解包,防止页面销毁时的崩溃问题。
内容的提问来源于stack exchange,提问作者Zim
相关产品推荐
相关产品推荐

