如何用RxSwift的RxCollectionViewSectionedReloadDataSource实现UICollectionView拖拽重排
基于RxSwift实现UICollectionView单元格拖拽重排方案
前置调整说明
你当前使用的RxCollectionViewSectionedReloadDataSource是全量刷新类型,不支持增量的移动动画,需要先替换为支持动画的RxCollectionViewSectionedAnimatedDataSource,同时将ViewModel中的数据源改为可读写的响应式类型(比如BehaviorRelay),方便拖拽后更新数据。
具体实现步骤
1. 调整ViewModel的数据源定义
将原来只读的items修改为可读写的BehaviorRelay,示例:
// ViewModel中定义,初始值根据你的业务逻辑赋值 let items = BehaviorRelay<[SectionModel<String, WorkGroup>]>(value: []) let disposeBag = DisposeBag()
2. 替换数据源并配置移动回调
替换原来的ReloadDataSource为AnimatedDataSource,同时实现moveItem闭包处理数据移动逻辑:
let dataSource = RxCollectionViewSectionedAnimatedDataSource<SectionModel<String, WorkGroup>>( configureCell: { (_, collectionView, indexPath, item) in guard let cell = collectionView .dequeueReusableCell(withReuseIdentifier: WorkGroupCell.identifier, for: indexPath) as? WorkGroupCell else { return WorkGroupCell() } cell.viewModel = item return cell }, // 新增移动item的回调,拖拽完成后会同步修改数据源 moveItem: { [weak self] dataSource, sourceIndexPath, destinationIndexPath in guard let self = self else { return } var updatedSections = self.viewModel.items.value // 取出要移动的元素 let movedItem = updatedSections[sourceIndexPath.section].items[sourceIndexPath.item] // 从原位置移除 updatedSections[sourceIndexPath.section].items.remove(at: sourceIndexPath.item) // 插入到目标位置 updatedSections[destinationIndexPath.section].items.insert(movedItem, at: destinationIndexPath.item) // 更新数据源触发UI刷新 self.viewModel.items.accept(updatedSections) } ) // 绑定逻辑和原来一致 viewModel.items.bind(to: tileCollectonView.rx.items(dataSource: dataSource)).disposed(by: disposeBag) tileCollectonView.rx.setDelegate(self).disposed(by: disposeBag)
3. 开启CollectionView拖拽功能并绑定事件
开启iOS自带的拖拽交互,同时通过Rx捕获拖拽事件:
// 开启拖拽交互 tileCollectonView.dragInteractionEnabled = true tileCollectonView.dragDelegate = self tileCollectonView.dropDelegate = self // 也可以通过Rx扩展简化代理实现,直接监听item移动事件 tileCollectonView.rx.itemMoved .subscribe(onNext: { [weak self] sourceIndexPath, destinationIndexPath in guard let self = self else { return } var updatedSections = self.viewModel.items.value let movedItem = updatedSections[sourceIndexPath.section].items[sourceIndexPath.item] updatedSections[sourceIndexPath.section].items.remove(at: sourceIndexPath.item) updatedSections[destinationIndexPath.section].items.insert(movedItem, at: destinationIndexPath.item) self.viewModel.items.accept(updatedSections) }) .disposed(by: disposeBag)
额外说明
- 如果不需要支持跨section拖拽,在移动逻辑里加判断:
guard sourceIndexPath.section == destinationIndexPath.section else { return }即可拦截跨section移动 - 拖拽的触发手势、拖拽过程中的cell样式可以通过自定义
UICollectionViewDragDelegate的相关方法调整
内容的提问来源于stack exchange,提问作者Prakash
相关产品推荐
相关产品推荐

