You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Bar Button Item接收拖放?实现集合视图单元格拖拽删除

解决导航栏垃圾桶BarButtonItem接收拖放的问题

确实,UIBarButtonItem不是UIView子类,没法直接添加UIDropInteraction,不过我们可以通过自定义UIView作为它的customView来绕开这个限制,下面是完整的实现方案:

步骤1:创建带拖放交互的垃圾桶自定义View

我们先做一个包含垃圾桶图标且支持拖放的UIView,之后把它作为BarButtonItem的customView:

class TrashBarButtonView: UIView {
    private let trashImageView: UIImageView = {
        let imageView = UIImageView(image: UIImage(systemName: "trash"))
        imageView.tintColor = .systemRed
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()
    
    // 用闭包传递删除事件给ViewController
    var onItemDropped: ((Any) -> Void)?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupLayout()
        addDropSupport()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupLayout()
        addDropSupport()
    }
    
    private func setupLayout() {
        addSubview(trashImageView)
        trashImageView.translatesAutoresizingMaskIntoConstraints = false
        
        // 约束设置成符合BarButtonItem的标准尺寸
        NSLayoutConstraint.activate([
            trashImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
            trashImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            trashImageView.widthAnchor.constraint(equalToConstant: 24),
            trashImageView.heightAnchor.constraint(equalToConstant: 24),
            widthAnchor.constraint(equalToConstant: 44),
            heightAnchor.constraint(equalToConstant: 44)
        ])
    }
    
    private func addDropSupport() {
        let dropInteraction = UIDropInteraction(delegate: self)
        addInteraction(dropInteraction)
    }
}

// 实现拖放代理方法
extension TrashBarButtonView: UIDropInteractionDelegate {
    func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
        // 这里替换成你实际的数据源类型,比如自定义Model
        return session.canLoadObjects(ofClass: NSString.self)
    }
    
    func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
        // 拖拽进入时给图标加高亮反馈
        trashImageView.tintColor = session.isActive ? .systemRed.withAlphaComponent(0.7) : .systemRed
        return UIDropProposal(operation: .move)
    }
    
    func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
        // 加载拖拽的数据并通知ViewController删除
        session.loadObjects(ofClass: NSString.self) { [weak self] items in
            guard let self = self, let targetItem = items.first else { return }
            self.onItemDropped?(targetItem)
        }
    }
    
    // 拖拽离开或结束时恢复图标颜色
    func dropInteraction(_ interaction: UIDropInteraction, sessionDidExit session: UIDropSession) {
        trashImageView.tintColor = .systemRed
    }
    
    func dropInteraction(_ interaction: UIDropInteraction, sessionDidEnd session: UIDropSession) {
        trashImageView.tintColor = .systemRed
    }
}

步骤2:在ViewController中配置导航栏和CollectionView

接下来把自定义的垃圾桶View设置成导航栏右侧按钮,同时给CollectionView添加拖拽支持:

class YourCollectionVC: UIViewController {
    private var collectionView: UICollectionView!
    private var dataSource: [String] = ["Item 1", "Item 2", "Item 3", "Item 4"] // 你的数据源
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupNavigationBar()
        setupCollectionView()
    }
    
    private func setupNavigationBar() {
        let trashView = TrashBarButtonView()
        // 绑定删除事件的处理逻辑
        trashView.onItemDropped = { [weak self] item in
            guard let self = self,
                  let itemToDelete = item as? String,
                  let index = self.dataSource.firstIndex(of: itemToDelete) else { return }
            
            // 更新数据源并刷新CollectionView
            self.dataSource.remove(at: index)
            self.collectionView.deleteItems(at: [IndexPath(item: index, section: 0)])
        }
        // 把自定义View包装成BarButtonItem
        let trashBarBtn = UIBarButtonItem(customView: trashView)
        navigationItem.rightBarButtonItem = trashBarBtn
    }
    
    private func setupCollectionView() {
        // 基础布局配置
        let layout = UICollectionViewFlowLayout()
        layout.itemSize = CGSize(width: view.bounds.width - 32, height: 60)
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
        collectionView.dataSource = self
        view.addSubview(collectionView)
        
        // 添加拖拽支持
        let dragInteraction = UIDragInteraction(delegate: self)
        collectionView.addInteraction(dragInteraction)
    }
}

// CollectionView数据源实现
extension YourCollectionVC: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        dataSource.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        cell.backgroundColor = .systemGray5
        // 添加单元格内容
        if let label = cell.viewWithTag(100) as? UILabel {
            label.text = dataSource[indexPath.item]
        } else {
            let label = UILabel(frame: cell.bounds.inset(by: UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)))
            label.tag = 100
            label.text = dataSource[indexPath.item]
            cell.addSubview(label)
        }
        return cell
    }
}

// CollectionView拖拽代理实现
extension YourCollectionVC: UIDragInteractionDelegate {
    func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
        guard let indexPath = collectionView.indexPathForItem(at: session.location(in: collectionView)),
              let targetItem = dataSource[safe: indexPath.item] else { return [] }
        
        // 将数据包装成NSItemProvider,自定义Model需要遵循NSItemProviderWriting协议
        let itemProvider = NSItemProvider(object: targetItem as NSString)
        let dragItem = UIDragItem(itemProvider: itemProvider)
        // 设置localObject方便后续快速获取数据
        dragItem.localObject = targetItem
        return [dragItem]
    }
}

// 数组安全下标扩展,避免越界崩溃
extension Array {
    subscript(safe index: Index) -> Element? {
        indices.contains(index) ? self[index] : nil
    }
}

关键注意点

  • 自定义Model适配:如果你的数据源是自定义Model,需要让Model遵循NSItemProviderWriting协议,这样才能在拖放时正确序列化和加载数据。
  • 视觉反馈:代码中添加了拖拽时的图标高亮,你还可以扩展更多交互反馈,比如缩放、阴影,提升用户体验。
  • 操作类型选择:如果希望拖拽后原单元格保留,可以把UIDropProposal的operation改成.copy,不过删除场景用.move更合理。

内容的提问来源于stack exchange,提问作者Giovanni Fabbretti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 10:02:51