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

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

相关推荐
OKXLIN6 小时前
IOS UITextField 无法隐藏键盘问题
ios·objective-c
AL.千灯学长14 小时前
DeepSeek接入Siri(已升级支持苹果手表)完整版硅基流动DeepSeek-R1部署
人工智能·gpt·ios·ai·苹果vision pro
openinstall全渠道统计1 天前
免填邀请码工具:赋能六大核心场景,重构App增长新模型
android·ios·harmonyos
早起的年轻人2 天前
Flutter CupertinoNavigationBar iOS 风格导航栏的组件
flutter·ios
貂蝉空大2 天前
uni-app开发安卓和ios app 真机调试
android·ios·uni-app
胖虎12 天前
iOS 中的圆角与平滑圆角:从新特性到老项目适配
ios·圆角·平滑圆角·cornercurve
志飞2 天前
ios UICollectionView使用自定义UICollectionViewCell
ios·collectionview·自定义cell
Neo Evolution2 天前
Flutter与移动开发的未来:谷歌的技术愿景与实现路径
android·人工智能·学习·ios·前端框架·webview·着色器
没头脑的ht3 天前
ios App的启动过程和启动优化
ios
敲代码的鱼哇3 天前
设备唯一ID获取,支持安卓/iOS/鸿蒙Next(uni-device-id)UTS插件
android·ios·uniapp·harmonyos