在 CollectionView 上滑动删除
Posted
技术标签:
【中文标题】在 CollectionView 上滑动删除【英文标题】:Swipe to delete on CollectionView 【发布时间】:2016-12-12 17:12:02 【问题描述】:我正在尝试复制 ios 的滑动删除功能。我知道它可以立即在 tableview 上使用,但是我需要构建的 UI 从 Collection View 中受益。因此,我需要一个自定义实现,我将使用向上滑动手势。幸运的是,这是我自己设法实现的,但是我很难弄清楚我需要如何设置滑动删除/点击删除/忽略功能。
用户界面当前如下所示:
所以我正在使用以下集合视图:
func buildCollectionView()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 0;
layout.minimumLineSpacing = 4;
collectionView = UICollectionView(frame: CGRect(x: 0, y: screenSize.midY - 120, width: screenSize.width, height: 180), collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(VideoCell.self, forCellWithReuseIdentifier: "videoCell")
collectionView.showsHorizontalScrollIndicator = false
collectionView.showsVerticalScrollIndicator = false
collectionView.contentInset = UIEdgeInsetsMake(0, 20, 0, 30)
collectionView.backgroundColor = UIColor.white()
collectionView.alpha = 0.0
//can swipe cells outside collectionview region
collectionView.layer.masksToBounds = false
swipeUpRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.deleteCell))
swipeUpRecognizer.delegate = self
collectionView.addGestureRecognizer(swipeUpRecognizer)
collectionView.isUserInteractionEnabled = true
我的自定义视频单元包含一个图像,其下方是删除按钮。因此,如果您向上滑动图像,则会弹出删除按钮。不确定这是否是正确的方法:
class VideoCell : UICollectionViewCell
var deleteView: UIButton!
var imageView: UIImageView!
override init(frame: CGRect)
super.init(frame: frame)
deleteView = UIButton(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height))
deleteView.contentMode = UIViewContentMode.scaleAspectFit
contentView.addSubview(deleteView)
imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height))
imageView.contentMode = UIViewContentMode.scaleAspectFit
contentView.addSubview(imageView)
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
我正在使用以下逻辑:
func deleteCell(sender: UIPanGestureRecognizer)
let tapLocation = sender.location(in: self.collectionView)
let indexPath = self.collectionView.indexPathForItem(at: tapLocation)
if velocity.y < 0
//detect if there is a swipe up and detect it's distance. If the distance is far enough we snap the cells Imageview to the top otherwise we drop it back down. This works fine already.
但问题从这里开始。一旦我的单元格超出 collectionview 边界,我就无法再访问它了。我仍然想进一步滑动它以将其删除。我只能通过滑动删除按钮来做到这一点,但我希望它上面的 Imageview 也可以滑动。或者,如果我点击 collectionview 之外的图像,它应该滑回该行而不是删除它。
如果我增加 collectionview 边界,我可以防止这个问题,但我也可以滑动以移除单元格的可见高度之外。这是由 collectionview 中的 tapLocation 引起的,它检测到 indexPath。我不想要的东西。我希望向上滑动只适用于 collectionview 的单元格。
按钮和图像也会相互干扰,因为我无法区分它们。它们都在同一个单元格中,这就是为什么我想知道是否应该在单元格中设置删除按钮。或者我应该把它放在哪里?我还可以用它制作两个按钮并根据状态禁用用户交互,但不确定最终会如何。
【问题讨论】:
【参考方案1】:因此,如果您希望滑动手势识别器在其位于集合视图之外时继续记录移动,则需要将其附加到集合视图的父级,因此它会限制在用户可以滑动的整个区域.
这确实意味着您会在集合视图之外获得滑动,但您可以很容易地忽略使用任意数量的技术的那些。
要注册删除按钮点击,您需要在按钮上调用 addTarget:action:forControlEvents:
我会将单元格保持原样,将图像和按钮放在一起。这将更容易管理,并且它们属于一起。
要管理上下移动图像,我会考虑使用变换或 NSLayoutConstraint。然后,您只需调整一个值,使其与用户滑动同步上下移动。不要乱用框架。
【讨论】:
让我跑下每条线。如果我将它附加到父视图,就像你说的那样,我会到处刷卡。问题是一旦我的单元格在我的collectionview 之外,我在deletecell 函数中的indexpath 将变为nil,我不能再拖动单元格了。增加我的collectionview 大小将使其成为可能,但它也将使用户能够在他们甚至没有触摸图像本身时拖动单元格。删除操作可以很容易地修复。唯一的问题是,图像视图的操作与删除按钮不同。当它们都可见时如何组合这两个动作? 我不确定,我想禁用集合视图剪辑到边界将允许您在其父级之外转换图像视图。myCollectionView.clipsToBounds = false
不,这不会奏效,因为如果视图在其范围内,视图只会将“触地”事件转发到子视图(collectionview 内的单元格)。所以增加界限会起作用,但这不是我想要的。还有其他建议吗?我可以覆盖 pointInside 方法,但这是我不想要的,因为我认为它不会返回我的 indexpath。
好吧,也许,正如您所说,让 UICollectionView 与可滑动区域一样大,但使用自定义 UICollectionViewLayout 将单元格保持在其原始位置,或者使用 UIScrollView 父类中的 contentInset。 【参考方案2】:
出于我自己的好奇心,我尝试复制您正在尝试做的事情,并让它以某种方式运作良好。它与您设置滑动手势的方式不同,因为我没有使用平移,但您说您已经拥有该部分,并且没有花费太多时间。 Pan 显然是让它交互的更可靠的解决方案,但计算时间更长,但它的效果和处理应该与我的示例相差不大。
为了解决无法在单元格外滑动的问题,我决定检查该点是否在滑动矩形中,这是非滑动矩形高度的两倍,如下所示:
let cellFrame = activeCell.frame
let rect = CGRectMake(cellFrame.origin.x, cellFrame.origin.y - cellFrame.height, cellFrame.width, cellFrame.height*2)
if CGRectContainsPoint(rect, point)
// If swipe point is in the cell delete it
let indexPath = myView.indexPathForCell(activeCell)
cats.removeAtIndex(indexPath!.row)
myView.deleteItemsAtIndexPaths([indexPath!])
我用 cmets 创建了一个演示:https://github.com/imbue11235/swipeToDeleteCell
无论如何,我希望它对您有所帮助!
【讨论】:
我当然对您的解决方案感兴趣。你能把更新的版本推送到github吗,因为我目前只有一个空项目! 哦,是的,这是一个错误,令人尴尬。现在更新了! 像魅力一样工作。正是我想要的。已经为我的 pangesture 重写了它,但实际上我正在研究如何增加单元格面积 :) 很高兴听到!很高兴我能帮忙:-)【参考方案3】:如果你想让它变得通用:
制作服装可滑动视图:
import UIKit
class SwipeView: UIView
lazy var label: UILabel =
let label = UILabel()
label.textColor = .black
label.backgroundColor = .green
return label
()
let visableView = UIView()
var originalPoint: CGPoint!
var maxSwipe: CGFloat! = 50
didSet(newValue)
maxSwipe = newValue
@IBInspectable var swipeBufffer: CGFloat = 2.0
@IBInspectable var highVelocity: CGFloat = 300.0
private let originalXCenter: CGFloat = UIScreen.main.bounds.width / 2
private var panGesture: UIPanGestureRecognizer!
public var isPanGestureEnabled: Bool
get return panGesture.isEnabled
set(newValue)
panGesture.isEnabled = newValue
override init(frame: CGRect)
super.init(frame: frame)
setupViews()
setupGesture()
private func setupViews()
addSubview(visableView)
visableView.addSubview(label)
visableView.edgesToSuperview()
label.edgesToSuperview()
private func setupGesture()
panGesture = UIPanGestureRecognizer(target: self, action: #selector(swipe(_:)))
panGesture.delegate = self
addGestureRecognizer(panGesture)
@objc func swipe(_ sender:UIPanGestureRecognizer)
let translation = sender.translation(in: self)
let newXPosition = center.x + translation.x
let velocity = sender.velocity(in: self)
switch(sender.state)
case .changed:
let shouldSwipeRight = translation.x > 0 && newXPosition < originalXCenter
let shouldSwipeLeft = translation.x < 0 && newXPosition > originalXCenter - maxSwipe
guard shouldSwipeRight || shouldSwipeLeft else break
center.x = newXPosition
case .ended:
if -velocity.x > highVelocity
center.x = originalXCenter - maxSwipe
break
guard center.x > originalXCenter - maxSwipe - swipeBufffer, center.x < originalXCenter - maxSwipe + swipeBufffer, velocity.x < highVelocity else
center.x = originalXCenter
break
default:
break
panGesture.setTranslation(.zero, in: self)
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
extension SwipeView: UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
return true
UICollectionViewCell 中嵌入可交换视图:
import UIKit
import TinyConstraints
protocol DeleteCellDelegate
func deleteCell(_ sender : UIButton)
class SwipeableCell: UICollectionViewCell
lazy var deleteButton: UIButton =
let button = UIButton()
button.backgroundColor = .red
button.addTarget(self, action: #selector(didPressedButton(_:)), for: .touchUpInside)
button.titleLabel?.text = "Delete"
return button
()
var deleteCellDelegate: DeleteCellDelegate?
@objc private func didPressedButton(_ sender: UIButton)
deleteCellDelegate?.deleteCell(sender)
print("delete")
let swipeableview: SwipeView =
return SwipeView()
()
override init(frame: CGRect)
super.init(frame: frame)
addSubview(deleteButton)
addSubview(swipeableview)
swipeableview.edgesToSuperview()
deleteButton.edgesToSuperview(excluding: .left, usingSafeArea: true)
deleteButton.width(bounds.width * 0.3)
swipeableview.maxSwipe = deleteButton.bounds.width
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
一个示例视图控制器:
import UIKit
import TinyConstraints
class ViewController: UIViewController, DeleteCellDelegate
func deleteCell(_ sender: UIButton)
let indexPath = IndexPath(item: sender.tag, section: 0)
items.remove(at: sender.tag)
collectionView.deleteItems(at: [indexPath])
lazy var collectionView: UICollectionView =
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: view.bounds.width, height: 40)
layout.sectionInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .yellow
cv.isPagingEnabled = true
cv.isUserInteractionEnabled = true
return cv
()
var items = ["1", "2", "3"]
override func viewDidLoad()
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.edgesToSuperview(usingSafeArea: true)
collectionView.register(SwipeableCell.self, forCellWithReuseIdentifier: "cell")
let panGesture = UIPanGestureRecognizer()
view.addGestureRecognizer(panGesture)
panGesture.delegate = self
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return items.count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SwipeableCell
cell.backgroundColor = .blue
cell.swipeableview.label.text = items[indexPath.item]
cell.deleteButton.tag = indexPath.item
cell.deleteCellDelegate = self
return cell
func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?)
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
extension ViewController: UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
return false
【讨论】:
以上是关于在 CollectionView 上滑动删除的主要内容,如果未能解决你的问题,请参考以下文章
在点击手势上重置 CollectionviewCell 位置
如何滚动到第一个单元格,同时在 CollectionView 上滑动最后一个单元格?
Swift - 使用CollectionView实现图片Gallery画廊效果(左右滑动浏览图片)