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

Swift中使用iOS11拖放协议实现CollectionView多元素批量重排问题

iOS 11 CollectionView 多选拖放重排解决方案

我太懂这种卡了两周的崩溃感!之前做项目时也在iOS 11的拖放多选重排上踩了一堆坑,终于摸出了稳定可行的方案,给你拆解一下关键步骤:

1. 先开启多选基础配置

首先得让CollectionView支持多选,这是实现的前提:

collectionView.allowsMultipleSelection = true

// 可选:给cell加选中状态的视觉反馈,让操作更直观
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let cell = collectionView.cellForItem(at: indexPath)
    cell?.backgroundColor = .lightGray // 选中高亮
}

func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
    let cell = collectionView.cellForItem(at: indexPath)
    cell?.backgroundColor = .white // 取消选中恢复原样
}

2. 拖拽会话:主动收集所有选中元素

默认的拖拽代理只会返回当前触摸的单个元素,我们要在这里主动抓取所有选中的indexPaths,生成对应的拖拽项:

func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
    // 如果用户没提前选中就拖拽,自动选中当前触摸的元素
    if !collectionView.indexPathsForSelectedItems?.contains(indexPath) ?? true {
        collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
    }
    
    // 获取所有选中的indexPaths并排序,避免后续数据乱序
    guard let selectedIndexPaths = collectionView.indexPathsForSelectedItems?.sorted() else {
        return []
    }
    
    // 为每个选中元素创建DragItem,并绑定原indexPath方便后续识别
    var dragItems = [UIDragItem]()
    for path in selectedIndexPaths {
        let item = yourDataSource[path.item] // 替换成你的数据源模型
        let itemProvider = NSItemProvider(object: item as! NSItemProviderWriting) // 模型需遵循NSItemProviderWriting协议
        let dragItem = UIDragItem(itemProvider: itemProvider)
        dragItem.localObject = path // 绑定原位置信息
        dragItems.append(dragItem)
    }
    
    // 拖拽开始时临时隐藏选中的cell,优化视觉体验
    selectedIndexPaths.forEach {
        collectionView.cellForItem(at: $0)?.isHidden = true
    }
    
    return dragItems
}

3. 拖放结束:批量更新数据源与UI

这是最容易出错的环节,重点要处理indexPath偏移问题,按顺序操作数据源:

func collectionView(_ collectionView: UICollectionView, performDropWithCoordinator coordinator: UICollectionViewDropCoordinator) {
    guard let destinationIndexPath = coordinator.destinationIndexPath else {
        // 拖放位置无效,恢复隐藏的cell
        collectionView.indexPathsForSelectedItems?.forEach {
            collectionView.cellForItem(at: $0)?.isHidden = false
        }
        return
    }
    
    // 取出拖拽项绑定的原indexPath
    let dragItems = coordinator.items.compactMap { $0.dragItem.localObject as? IndexPath }
    guard !dragItems.isEmpty else { return }
    
    // 按原顺序取出要移动的元素
    let itemsToMove = dragItems.sorted().map { yourDataSource[$0.item] }
    
    // 从数据源移除元素:必须从后往前删,避免前面的元素删除后导致后面的indexPath偏移
    dragItems.sorted(by: { $0.item > $1.item }).forEach {
        yourDataSource.remove(at: $0.item)
    }
    
    // 调整插入位置:如果目标位置在原选中元素范围内,要扣除已删除元素的数量
    let adjustedDestinationIndex = destinationIndexPath.item - dragItems.filter { $0.item < destinationIndexPath.item }.count
    
    // 插入元素到新位置
    yourDataSource.insert(contentsOf: itemsToMove, at: adjustedDestinationIndex)
    
    // 批量更新CollectionView,同步UI与数据源
    collectionView.performBatchUpdates({
        // 删除原选中项
        collectionView.deleteItems(at: dragItems)
        // 插入新位置的项
        let newIndexPaths = (adjustedDestinationIndex..<adjustedDestinationIndex+itemsToMove.count).map {
            IndexPath(item: $0, section: destinationIndexPath.section)
        }
        collectionView.insertItems(at: newIndexPaths)
    }, completion: { _ in
        // 更新选中状态,选中新位置的元素
        collectionView.indexPathsForSelectedItems?.forEach {
            collectionView.deselectItem(at: $0, animated: false)
        }
        newIndexPaths.forEach {
            collectionView.selectItem(at: $0, animated: true, scrollPosition: [])
        }
    })
    
    // 完成拖放动画
    coordinator.drop(coordinator.items.first?.dragItem, toItemAt: destinationIndexPath)
}

4. 避坑关键提醒

  • 数据源操作顺序:先删后插,且删除时从后往前,彻底避免indexPath偏移导致的数据错乱。
  • 无需占位符:你之前用占位符出错是正常的,它本来是为远程异步加载设计的,本地多选重排直接操作本地数据源+批量更新就足够。
  • 绑定原位置:通过dragItem.localObject绑定原indexPath,能在拖放时精准识别要移动的元素,不会搞混数据。

内容的提问来源于stack exchange,提问作者lozflan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:56:58