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

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

相关推荐
若水无华11 小时前
fiddler 配置ios手机代理调试
ios·智能手机·fiddler
Aress"13 小时前
【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
ios·uni-app·ipa安装
Jouzzy1 天前
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
安全·ios·iphone
瓜子三百克1 天前
采用sherpa-onnx 实现 ios语音唤起的调研
macos·ios·cocoa
左钦杨1 天前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
努力成为包租婆1 天前
SDK does not contain ‘libarclite‘ at the path
ios
安和昂2 天前
【iOS】Tagged Pointer
macos·ios·cocoa
I烟雨云渊T2 天前
iOS 阅后即焚功能的实现
macos·ios·cocoa
struggle20252 天前
适用于 iOS 的 开源Ultralytics YOLO:应用程序和 Swift 软件包,用于在您自己的 iOS 应用程序中运行 YOLO
yolo·ios·开源·app·swift
Unlimitedz2 天前
iOS视频编码详细步骤(视频编码器,基于 VideoToolbox,支持硬件编码 H264/H265)
ios·音视频