能否在多列UICollectionView中实现侧滑删除操作?
实现多列UICollectionView的侧滑删除功能
你不需要在多列布局和侧滑删除之间二选一,完全可以同时实现。系统的trailingSwipeActionsConfigurationProvider确实仅适配UICollectionViewCompositionalLayout.list创建的列表布局,但你可以通过自定义UICollectionViewCell并添加滑动手势来实现侧滑删除功能,具体步骤如下:
一、自定义Cell实现侧滑逻辑
- 给Cell添加内容容器视图,将原本的Cell内容全部放入该容器中
- 在Cell的contentView中添加删除按钮,初始时位于容器视图右侧(不可见)
- 给内容容器添加
UIPanGestureRecognizer,监听横向拖动手势 - 根据拖动偏移量调整容器视图的位置,显示删除按钮;当拖动超过阈值时触发删除逻辑
代码示例
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
相关产品推荐
相关产品推荐

