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

能否在多列UICollectionView中实现侧滑删除操作?

实现多列UICollectionView的侧滑删除功能

你不需要在多列布局和侧滑删除之间二选一,完全可以同时实现。系统的trailingSwipeActionsConfigurationProvider确实仅适配UICollectionViewCompositionalLayout.list创建的列表布局,但你可以通过自定义UICollectionViewCell并添加滑动手势来实现侧滑删除功能,具体步骤如下:

一、自定义Cell实现侧滑逻辑

  1. 给Cell添加内容容器视图,将原本的Cell内容全部放入该容器中
  2. 在Cell的contentView中添加删除按钮,初始时位于容器视图右侧(不可见)
  3. 给内容容器添加UIPanGestureRecognizer,监听横向拖动手势
  4. 根据拖动偏移量调整容器视图的位置,显示删除按钮;当拖动超过阈值时触发删除逻辑

代码示例

class CustomGridCell: UICollectionViewCell {
    // 内容容器,承载Cell的所有展示内容
    private let contentContainer = UIView()
    // 删除按钮
    private let deleteBtn = UIButton(type: .system)
    
    // 删除回调,用于通知ViewController执行数据源更新
    var onDelete: (() -> Void)?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
        setupGesture()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupUI() {
        // 配置内容容器
        contentView.addSubview(contentContainer)
        contentContainer.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            contentContainer.topAnchor.constraint(equalTo: contentView.topAnchor),
            contentContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            contentContainer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            contentContainer.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
        contentContainer.backgroundColor = .white
        contentContainer.layer.cornerRadius = 8
        contentContainer.clipsToBounds = true
        
        // 配置删除按钮
        deleteBtn.setTitle("删除", for: .normal)
        deleteBtn.backgroundColor = .systemRed
        deleteBtn.setTitleColor(.white, for: .normal)
        deleteBtn.addTarget(self, action: #selector(deleteTapped), for: .touchUpInside)
        contentView.addSubview(deleteBtn)
        deleteBtn.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            deleteBtn.topAnchor.constraint(equalTo: contentView.topAnchor),
            deleteBtn.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            deleteBtn.widthAnchor.constraint(equalToConstant: 80),
            deleteBtn.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
    }
    
    private func setupGesture() {
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
        contentContainer.addGestureRecognizer(panGesture)
    }
    
    @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
        let translationX = gesture.translation(in: contentView).x
        let maxSlideDistance: CGFloat = -80 // 对应删除按钮的宽度
        
        switch gesture.state {
        case .changed:
            // 限制滑动范围,避免过度拖动
            let targetX = max(translationX, maxSlideDistance)
            contentContainer.transform = CGAffineTransform(translationX: targetX, y: 0)
        case .ended, .cancelled:
            // 判断是否触发删除:滑动距离超过阈值(这里设为按钮宽度的一半)
            let shouldTriggerDelete = translationX < maxSlideDistance * 0.5
            if shouldTriggerDelete {
                UIView.animate(withDuration: 0.25, animations: {
                    self.contentContainer.transform = CGAffineTransform(translationX: maxSlideDistance, y: 0)
                }, completion: { _ in
                    self.onDelete?()
                    // 执行完删除后重置位置,避免复用Cell时状态异常
                    self.contentContainer.transform = .identity
                })
            } else {
                // 回弹动画
                UIView.animate(withDuration: 0.2) {
                    self.contentContainer.transform = .identity
                }
            }
        default:
            break
        }
    }
    
    @objc private func deleteTapped() {
        onDelete?()
        // 点击删除按钮后回弹
        UIView.animate(withDuration: 0.2) {
            self.contentContainer.transform = .identity
        }
    }
    
    // 复用Cell时重置状态
    override func prepareForReuse() {
        super.prepareForReuse()
        contentContainer.transform = .identity
    }
}

二、在ViewController中配置回调

在Cell的cellForItemAt方法中设置删除回调,更新数据源并刷新CollectionView:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomGridCell", for: indexPath) as! CustomGridCell
    // 配置Cell内容...
    cell.onDelete = { [weak self] in
        guard let self = self else { return }
        // 删除数据源对应的数据
        self.dataList.remove(at: indexPath.item)
        // 刷新CollectionView,带动画
        collectionView.deleteItems(at: [indexPath])
    }
    return cell
}

三、注意事项

  • 处理Cell复用:在prepareForReuse中重置容器视图的位置,避免复用Cell时保留之前的滑动状态
  • 手势冲突:如果Cell内有其他横向滑动的控件(如UIScrollView),需要处理手势优先级,避免冲突
  • 动画流畅性:使用UIView动画保证滑动和回弹过程的流畅性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 17:45:59