卡片拖拽效果 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
}

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

相关推荐
二流小码农18 小时前
鸿蒙开发:web页面如何适配深色模式
android·ios·harmonyos
yuec21 小时前
iOS 26 你的 property 崩了吗?
ios·客户端
jiangmiao20241 天前
IOS开发 Runloop机制
ios·objective-c
從南走到北1 天前
JAVA国际版任务悬赏发布接单系统源码支持IOS+Android+H5
android·java·ios·微信·微信小程序·小程序
咕噜签名分发冰淇淋1 天前
苹果ios安卓apk应用APP文件怎么修改手机APP显示的名称
android·ios·智能手机
游戏开发爱好者81 天前
iOS 开发推送功能全流程详解 从 APNs 配置到上架发布的完整实践(含跨平台上传方案)
android·macos·ios·小程序·uni-app·cocoa·iphone
Larva1 天前
iOS - 关于如何在编译时写入文件并在代码内读取文件内容
ios
胎粉仔2 天前
Objective-C 初阶 —— __bridge & __bridge_retained & __bridge_transfer
ios·objective-c
笑尘pyrotechnic2 天前
【OC】UIKit常用组件适配iOS 26
macos·ios·cocoa