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

自定义按钮删除UICollectionViewCell异常:删除随机单元格问题排查

问题分析:错误的IndexPath存储方式导致删除随机Cell

你遇到的问题核心在于UIButton的extension里用了静态变量存储indexPath——Holder._myComputedProperty是静态的,这意味着所有按钮实例都会共享同一个值。当UICollectionView复用Cell时,新的Cell会覆盖这个静态变量的indexPath,所以不管你点哪个删除按钮,拿到的都是最后一次被设置的indexPath,自然会删掉错误的Cell。

要解决这个问题,我们需要给每个按钮单独存储自己对应的indexPath,而不是用全局共享的静态变量。下面给两种可行的修复方案:


方案一:使用关联对象(Associated Objects)正确存储IndexPath

这是对原有写法的修正,把静态变量换成每个按钮实例独有的关联对象:

import ObjectiveC

// 定义关联对象的唯一Key
private var IndexPathKey: UInt8 = 0

extension UIButton {
    var indexPath: IndexPath! {
        get {
            return objc_getAssociatedObject(self, &IndexPathKey) as? IndexPath
        }
        set {
            objc_setAssociatedObject(self, &IndexPathKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

这样每个按钮都会单独保存自己的indexPath,不会被其他按钮覆盖。剩下的删除逻辑代码可以保持不变,就能正确找到目标Cell了。


方案二:用闭包替代Target-Action(更推荐)

在Cell里给删除按钮添加闭包回调,直接捕获当前的indexPath,完全避免复用带来的变量污染问题,代码也更清晰易维护:

第一步:修改RequestCell,添加闭包属性

class RequestCell: UICollectionViewCell {
    // 假设你的删除按钮叫deleteButton
    @IBOutlet weak var deleteButton: UIButton!
    
    // 定义删除回调闭包
    var onDeleteTap: ((IndexPath) -> Void)?
    
    override func prepareForReuse() {
        super.prepareForReuse()
        // 复用前清空闭包,避免旧的回调被错误触发
        onDeleteTap = nil
    }
}

第二步:在cellForItemAt中设置闭包

public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! RequestCell
    
    // 先移除旧的target绑定(如果之前用了target-action)
    cell.deleteButton.removeTarget(nil, action: nil, for: .allEvents)
    
    // 设置闭包,直接捕获当前的indexPath
    cell.onDeleteTap = { [weak self] path in
        guard let self = self else { return }
        self.arrRequest.remove(at: path.item)
        self.collectionView.performBatchUpdates({
            self.collectionView.deleteItems(at: [path])
        }, completion: nil)
        // 这里不需要额外调用reloadVisibleItems,performBatchUpdates已经会正确更新UI
    }
    
    // 给按钮添加点击事件,触发闭包
    cell.deleteButton.addTarget(cell, action: #selector(RequestCell.handleDeleteTap(_:)), for: .touchUpInside)
    
    return cell
}

第三步:在RequestCell中实现触发闭包的方法

extension RequestCell {
    @objc func handleDeleteTap(_ sender: UIButton) {
        // 获取当前cell对应的indexPath
        guard let collectionView = self.superview as? UICollectionView,
              let indexPath = collectionView.indexPath(for: self) else { return }
        // 触发闭包
        onDeleteTap?(indexPath)
    }
}

这种方式的好处是,闭包在创建时就捕获了当前Cell对应的indexPath,完全不会受Cell复用的影响,是更现代、更安全的写法。


另外补充一点:你在删除完成后调用reloadItems(at: collectionView.indexPathsForVisibleItems)其实是多余的,performBatchUpdates已经会正确更新UICollectionView的布局和显示,不需要额外reload。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:18:17