卡片拖拽效果 collectionView、drag

最近项目中需要实现个类似苹果手机桌面上,APP拖拽的效果。拖拽到指定的位置,更新位置并调整布局。记录下实现思路

collectionView基本使用

swift 复制代码
let layout = UICollectionViewFlowLayout()
        
collection = UICollectionView(frame: UIScreen.main.bounds, collectionViewLayout: layout)
collection.backgroundColor = .white
collection.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "CELL")

collection.dataSource = self
collection.delegate = self

view.addSubview(collection)

// 实现代理
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return items.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CELL", for: indexPath)
    cell.backgroundColor = .orange

    let image = UIImageView(image: UIImage(named: items[indexPath.row])!)

    cell.contentView.addSubview(image)
    image.snp.makeConstraints { make in
        make.edges.equalTo(cell.contentView)
    }
    return cell
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: ((collectionView.frame.width - 32) / 2), height: ((collectionView.frame.width - 32) / 3))
}

实现拖拽效果

collectionView有个叫dragDelegate的protocol

An interface that you implement to interact with a drop operation in a view modified to accept drops.

实现这个protocol的collectionView(_ collectionView: UICollectionView, itemsForBeginning session: any UIDragSession, at indexPath: IndexPath) -> [UIDragItem]方法显示拖拽效果

swift 复制代码
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
    let item = self.items[indexPath.row]
    let itemProvider = NSItemProvider(object: item as NSString)
    let dragItem = UIDragItem(itemProvider: itemProvider)
    dragItem.localObject = item
    return [dragItem]
}

实现这个方法之后,collectionView就有了可以拖拽的效果

但拖拽后item并不会在松开拖拽的位置保存下来,还需要实例另一个protocol的代理方法

dropDelegate

官方解释:The delegate object that manages the dropping of items into the collection view.

func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal 实现该方法,告诉DropDelegate当我们drag一个cell到一个新位置的时候会发生什么

func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)该方法用于计算drag的cell是否越界、目的地对应的indexPath以及要自己实现的重新排列collectionView的规则

swift 复制代码
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
    if collectionView.hasActiveDrag {
        return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
    }
    return UICollectionViewDropProposal(operation: .forbidden)
}


func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
    var destinationIndePath: IndexPath
    if let indexPath = coordinator.destinationIndexPath {
        destinationIndePath = indexPath
    } else {
        let row = collectionView.numberOfItems(inSection: 0)
        destinationIndePath = IndexPath(item: row - 1, section: 0)
    }
    if coordinator.proposal.operation == .move {
        self.reorderItems(coordinator: coordinator, destinationIndexPath: destinationIndePath, collectionView: collectionView)
    }
}

reorderItems方法实现如下

swift 复制代码
fileprivate func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
    if let item = coordinator.items.first,
       let sourceIndexPath = item.sourceIndexPath {
        collectionView.performBatchUpdates({
            self.items.remove(at: sourceIndexPath.item)
            self.items.insert(item.dragItem.localObject as! String, at: destinationIndexPath.item)
            
            collectionView.deleteItems(at: [sourceIndexPath])
            collectionView.insertItems(at: [destinationIndexPath])
        }, completion: nil)
        coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
    }
    
}

调用collectionView.performBatchUpdates和coordinator.drop实现被drag的cell放置在指定的indexPath上

其他补充

有时自己自定义的cell可能会有一些类似圆角、阴影之类的效果,drag的时候,会默认给一个拖拽的背景效果,导致一些不和谐的UI出来,可以实现两个方法来取消这种默认效果

swift 复制代码
func collectionView(_ collectionView: UICollectionView, dropPreviewParametersForItemAt indexPath: IndexPath) -> UIDragPreviewParameters? {
    let previewParameters = UIDragPreviewParameters()
    
    // 设置透明背景以移除白色背景
    previewParameters.backgroundColor = .clear
    previewParameters.shadowPath = UIBezierPath(rect: .zero) // 隐藏阴影
    
    return previewParameters
}

func collectionView(_ collectionView: UICollectionView, dragPreviewParametersForItemAt indexPath: IndexPath) -> UIDragPreviewParameters? {
    let previewParameters = UIDragPreviewParameters()
    
    // 设置透明背景以移除白色背景
    previewParameters.backgroundColor = .clear
    previewParameters.shadowPath = UIBezierPath(rect: .zero) // 隐藏阴影
    
    return previewParameters
}

这两个分别是drop和drag 里相关的方法,我的实践是都要实现一遍

如果要实现一个collectionView中,某些cell不需要drag,一个简单的方式是在func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]中,判断出不需要drag的cell后直接返回个空数组即可。

如果你的collectionView的dataSource是动态变化的,在上面的reorderItems方法中,最好做下边界值的越界判断

swift 复制代码
if destinationIndexPath.row >= self.data?.count ?? 0 {
    return
}

其他可能出现的坑,请随时评论

相关推荐
笑尘pyrotechnic12 小时前
LLDB进阶:使用命令行进行检查
ios·objective-c·cocoa·lldb
z***y86215 小时前
Swift在iOS中的Xcode
ios·xcode·swift
AirDroid_cn15 小时前
iOS 18 后台应用偷跑流量,如何限制?
macos·ios·cocoa
明君8799718 小时前
Flutter 图纸标注功能的实现:踩坑与架构设计
android·ios
江东小bug王18 小时前
深入理解 UINavigationController:生命周期、动画优化与性能调优
ios
Lexiaoyao2019 小时前
Apple StoreKit 2 开发指南
ios·apple
2501_915106322 天前
iOS App 测试工具全景分析,构建从开发调试到线上监控的多阶段工具链体系
android·测试工具·ios·小程序·uni-app·iphone·webview
Digitally2 天前
如何通过蓝牙将联系人从 iPhone 传输到 Android
android·ios·iphone
90后的晨仔2 天前
2025年11月27日年解决隐私清单导致审核总是提示二进制无效的问题
ios
songgeb2 天前
iOS Audio后台模式下能否执行非Audio逻辑
ios·swift