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

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

相关推荐
CHB4 小时前
uni-app x 蒸汽模式 性能测试基准报告 Benchmark【Android版】
android·ios·uni-app
Magic-ZYJ5 小时前
不登录、不联网也能用:我是如何设计一款“本地优先”iOS App 的
ios·app·iphone·研发·独立开发者·心情日记
_阿南_7 小时前
flutter 展示的字体突然奔放了
android·flutter·ios
90后的晨仔11 小时前
你的 iOS 最低部署版本即将不再被 App Store 接受
ios
2601_9657984714 小时前
Launch Your Wellness App Fast: The Truth About Meditation Source Code
前端·macos·ios·objective-c·cocoa
初级代码游戏1 天前
iOS开发 Swift 速记7:结构体和类
开发语言·ios·swift
Anhty1 天前
手机端叮咚变声器2026实测:功能表现、优缺点完整解析
功能测试·ios·智能手机·安卓
SameX2 天前
后台定位耗电的问题,我花两年把它压到了3%-5%
ios
星辰即远方2 天前
计算器仿写总结
学习·ui·ios·cocoa·xcode
游戏开发爱好者82 天前
系统状态:专为iPhone和iPad设计的实时系统性能监控工具全面介绍
android·ios·小程序·uni-app·iphone·webview·ipad