UIKit横向滑动卡片功能异常求助:左滑移除/右滑追加问题
修复UIKit横向滑动卡片(左滑移除/右滑移至末尾)的问题
核心问题分析
你的代码存在几个关键问题导致功能失效:
- 手势总位移计算错误:每次移动后重置了手势
translation,导致结束时无法获取完整滑动距离,阈值判断失效 - 堆叠卡片的层级错误:未设置
zIndex,下层卡片可能拦截手势,且显示层级混乱 - 右滑逻辑缺失:仅重置卡片位置,未实现将卡片移至数据源末尾的核心操作
- 卡片位置重置错误:使用
collectionView.center(父视图坐标系)而非cell所在的collectionView内部坐标系 - 手势范围失控:给整个collectionView加手势,导致下层卡片也能响应滑动
分步修复方案
1. 修复手势位移计算
添加属性记录总滑动距离,避免因重置translation丢失完整位移数据,同时限制仅最上层卡片响应手势:
// 添加属性记录手势总位移 private var totalPanX: CGFloat = 0 @objc func handlePanGesture(_ gesture: UIPanGestureRecognizer) { let location = gesture.location(in: collectionView) // 仅允许最上层卡片(index=0)响应手势 guard let indexPath = collectionView.indexPathForItem(at: location), indexPath.item == 0, let cell = collectionView.cellForItem(at: indexPath) as? SwipeCardCell else { return } let translation = gesture.translation(in: collectionView) totalPanX += translation.x cell.center.x += translation.x gesture.setTranslation(.zero, in: collectionView) if gesture.state == .ended { let threshold: CGFloat = 100 let direction: CGFloat = totalPanX > 0 ? 1 : -1 if abs(totalPanX) > threshold { if direction < 0 { swipeLeftAction(cell, indexPath) } else { swipeRightAction(cell, indexPath) } } else { UIView.animate(withDuration: 0.3) { // 使用collectionView内部的中点坐标 cell.center = CGPoint(x: self.collectionView.bounds.midX, y: self.collectionView.bounds.midY) } } // 重置总位移 totalPanX = 0 } }
2. 修复卡片层级(TinderLayout)
在布局属性中设置zIndex,确保最上层卡片(索引最小)拥有最高显示层级:
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { guard let collectionView = collectionView else { return nil } let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath) attr.size = itemSize // 使用collectionView内部的中点坐标 let center = CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY) attr.center = center // 调整堆叠位置,同时设置zIndex确保上层卡片在最前面 let itemOffset = CGFloat(indexPath.item) * interItemSpacing attr.transform = CGAffineTransform(translationX: 0, y: -itemOffset) // 索引越小,zIndex越高,显示在最上层 attr.zIndex = collectionView.numberOfItems(inSection: 0) - indexPath.item return attr }
3. 实现右滑追加逻辑
修改swipeRightAction,完成将当前卡片移至数据源末尾的核心操作:
func swipeRightAction(_ cell: SwipeCardCell, _ indexPath: IndexPath) { UIView.animate(withDuration: 0.3, animations: { cell.center = CGPoint(x: self.collectionView.bounds.midX, y: self.collectionView.bounds.midY) }, completion: { _ in // 将当前卡片移至数据源末尾 let movedCard = self.dataSource.remove(at: indexPath.item) self.dataSource.append(movedCard) // 刷新collectionView触发布局更新 self.collectionView.reloadData() }) }
4. 优化移除卡片后的布局
删除卡片后强制刷新布局,确保下层卡片正确上移:
func swipeLeftAction(_ cell: SwipeCardCell, _ indexPath: IndexPath) { UIView.animate(withDuration: 0.3, animations: { cell.center.x -= 1000 }, completion: { _ in self.removedCards.append((self.dataSource[indexPath.item], indexPath)) self.dataSource.remove(at: indexPath.item) self.collectionView.deleteItems(at: [indexPath]) // 强制布局更新,确保下层卡片上移 self.collectionView.collectionViewLayout.invalidateLayout() }) }
完整修复后的代码
class SwipeCardComponentVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate { @IBOutlet weak var collectionView: UICollectionView! var dataSource = ["Card 1", "Card 2", "Card 3", "Card 4", "Card 5"] var removedCards: [(card: String, indexPath: IndexPath)] = [] private var totalPanX: CGFloat = 0 // 记录手势总位移 override func viewDidLoad() { super.viewDidLoad() let layout = TinderLayout() collectionView.collectionViewLayout = layout collectionView.dataSource = self collectionView.delegate = self let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) collectionView.addGestureRecognizer(panGesture) } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return dataSource.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SwipeCardCell", for: indexPath) as! SwipeCardCell let color = ["DBA979","BACD92","9BB0C1","8E7AB5","80BCBD","C3E2C2","AFC8AD","D2E0FB"] cell.bgView.backgroundColor = hexStringToUIColor(hex: color[indexPath.row % color.count]) cell.center = CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY) return cell } @objc func handlePanGesture(_ gesture: UIPanGestureRecognizer) { let location = gesture.location(in: collectionView) // 仅允许最上层卡片(index=0)响应手势 guard let indexPath = collectionView.indexPathForItem(at: location), indexPath.item == 0, let cell = collectionView.cellForItem(at: indexPath) as? SwipeCardCell else { return } let translation = gesture.translation(in: collectionView) totalPanX += translation.x cell.center.x += translation.x gesture.setTranslation(.zero, in: collectionView) if gesture.state == .ended { let threshold: CGFloat = 100 let direction: CGFloat = totalPanX > 0 ? 1 : -1 if abs(totalPanX) > threshold { if direction < 0 { swipeLeftAction(cell, indexPath) } else { swipeRightAction(cell, indexPath) } } else { UIView.animate(withDuration: 0.3) { cell.center = CGPoint(x: self.collectionView.bounds.midX, y: self.collectionView.bounds.midY) } } totalPanX = 0 } } func swipeLeftAction(_ cell: SwipeCardCell, _ indexPath: IndexPath) { UIView.animate(withDuration: 0.3, animations: { cell.center.x -= 1000 }, completion: { _ in self.removedCards.append((self.dataSource[indexPath.item], indexPath)) self.dataSource.remove(at: indexPath.item) self.collectionView.deleteItems(at: [indexPath]) self.collectionView.collectionViewLayout.invalidateLayout() }) } func swipeRightAction(_ cell: SwipeCardCell, _ indexPath: IndexPath) { UIView.animate(withDuration: 0.3, animations: { cell.center = CGPoint(x: self.collectionView.bounds.midX, y: self.collectionView.bounds.midY) }, completion: { _ in let movedCard = self.dataSource.remove(at: indexPath.item) self.dataSource.append(movedCard) self.collectionView.reloadData() }) } } class TinderLayout: UICollectionViewLayout { private let itemSize = CGSize(width: 400, height: 600) private let interItemSpacing: CGFloat = 10 override func prepare() { super.prepare() } override var collectionViewContentSize: CGSize { guard let collectionView = collectionView else { return .zero } return collectionView.bounds.size } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let collectionView = collectionView else { return nil } var attributes: [UICollectionViewLayoutAttributes] = [] let numberOfItems = collectionView.numberOfItems(inSection: 0) for index in 0..<numberOfItems { let indexPath = IndexPath(item: index, section: 0) if let attr = layoutAttributesForItem(at: indexPath) { attributes.append(attr) } } return attributes } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { guard let collectionView = collectionView else { return nil } let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath) attr.size = itemSize let center = CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY) attr.center = center let itemOffset = CGFloat(indexPath.item) * interItemSpacing attr.transform = CGAffineTransform(translationX: 0, y: -itemOffset) // 设置zIndex确保上层卡片显示在最前面 attr.zIndex = collectionView.numberOfItems(inSection: 0) - indexPath.item return attr } }
内容的提问来源于stack exchange,提问作者Ratnesh Pathak
相关产品推荐
相关产品推荐

