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

如何正确实现每个Cell带删除按钮的CollectionView?

CollectionView Cell 添加删除按钮的最优方案分析

先直接给结论:方案1(自定义Cell)的思路更靠谱,但可以通过优化避免“每个Cell类型都要子类化”的问题;方案2不推荐,因为它违背了MVC的职责划分,且复用逻辑容易出bug。

对现有两种方案的分析

  • 方案1(自定义Cell子类):
    缺点确实是需要为每种现有Cell创建子类,但优点是UI封装性强,复用逻辑清晰,不会把UI修改逻辑混入数据源方法。这个缺点其实是可以解决的,不用硬给每个Cell做子类。

  • 方案2(在cellForItemAt中添加按钮):
    完全不推荐。数据源方法的核心职责是提供数据、配置Cell的数据内容,把UI修改(加按钮)放在这里会让代码职责混乱,后期维护难度大。另外,Cell复用时用标签判断按钮是否存在的方式风险很高,很容易和第三方库的标签冲突,导致按钮重复添加或者找不到的问题。

更优的实现方式

推荐两种更实用的思路,不用修改现有Cell结构,也能优雅地添加删除按钮:

1. 通用装饰Cell(推荐)

创建一个专门的DeleteableCollectionViewCell,作为“容器”来包裹原有Cell:

  • 这个容器Cell内部包含一个contentContainerView(用来承载原有Cell的内容)和一个删除按钮;
  • 在cellForItemAt方法中,先获取这个容器Cell,再获取原本需要展示的目标Cell,把目标Cell添加到contentContainerView中,设置约束让目标Cell充满容器;
  • 删除按钮的点击逻辑统一在容器Cell中处理,通过代理或者闭包通知外部删除事件。

这种方式的好处是:原有Cell完全不用修改,不管有多少种Cell类型,都可以用同一个容器Cell来添加删除按钮,彻底解决“每个Cell都要子类化”的问题,同时保持UI逻辑的封装性。

2. UICollectionViewCell 扩展(轻量方案)

通过给UICollectionViewCell写扩展,用关联对象存储删除按钮,避免重复创建:

import UIKit

private var deleteButtonAssociatedKey = "deleteButtonAssociatedKey"
private var deleteActionKey = "deleteActionKey"

extension UICollectionViewCell {
    private var deleteButton: UIButton? {
        get {
            return objc_getAssociatedObject(self, &deleteButtonAssociatedKey) as? UIButton
        }
        set {
            objc_setAssociatedObject(self, &deleteButtonAssociatedKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    private var deleteAction: (() -> Void)? {
        get { return objc_getAssociatedObject(self, &deleteActionKey) as? () -> Void }
        set { objc_setAssociatedObject(self, &deleteActionKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) }
    }
    
    func configDeleteButton(withAction action: @escaping () -> Void) {
        guard deleteButton == nil else {
            // 复用的时候更新点击事件
            deleteButton?.actions(forTarget: self, forControlEvent: .touchUpInside)?.forEach {
                removeTarget(self, action: Selector($0), for: .touchUpInside)
            }
            deleteButton?.addTarget(self, action: #selector(deleteButtonTapped(_:)), for: .touchUpInside)
            self.deleteAction = action
            return
        }
        
        let button = UIButton(type: .system)
        button.setTitle("删除", for: .normal)
        button.backgroundColor = .red
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 4
        // 固定按钮位置,比如右上角
        button.frame = CGRect(x: bounds.width - 65, y: 8, width: 60, height: 30)
        button.addTarget(self, action: #selector(deleteButtonTapped(_:)), for: .touchUpInside)
        addSubview(button)
        bringSubviewToFront(button)
        
        self.deleteButton = button
        self.deleteAction = action
    }
    
    @objc private func deleteButtonTapped(_ sender: UIButton) {
        deleteAction?()
    }
}

使用时,在cellForItemAt中只需要调用:

cell.configDeleteButton { [weak self] in
    // 处理删除逻辑,比如删除数据源对应的数据,刷新CollectionView
    guard let self = self else { return }
    self.dataSource.remove(at: indexPath.item)
    self.collectionView.deleteItems(at: [indexPath])
}

这种方式不用创建新的Cell子类,也不会污染数据源方法的职责,复用的时候也能正确处理按钮的状态和事件。

总结

  • 如果项目中Cell类型较多,优先用通用装饰Cell,结构更清晰,扩展性更强;
  • 如果只是简单给少数Cell加删除按钮,用Cell扩展的方式更轻量;
  • 绝对不要用方案2,后期维护会有一堆坑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 09:52:44