You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 06:45:00