Swipe to delete on CollectionView

Wouter125 picture Wouter125 · Aug 6, 2016 · Viewed 10.9k times · Source

I'm trying to replicate the swipe to delete functionality of iOS. I know it's instantly available on a tableview, but the UI that I need to build benefits from a Collection View. Therefor I need a custom implementation where I would be using a swipe up gesture. Luckily, that's something that I managed to implement myself, however I'm having a hard time figuring out how I need to setup the swipe to delete / tap to delete / ignore functionality.

The UI currently looks like this: Delete Image

So I'm using the following collectionview:

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
}

My custom videocell contains one image and below that there is the delete button. So if you swipe the image up the delete button pops up. Not sure if this is the right way on how to do it:

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")
}
}

And I'm using the following logic:

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.
    }
}

But the problem starts there. As soon as my cell is outside the collectionview bounds I can't access it anymore. I still want to swipe it further to remove it. I can only do this by swiping on the delete button, but I want the Imageview above it to be swipeable as well. Or if I tap the image outside the collectionview it should slide back into the line and not delete it.

If I increase the collectionview bounds I can prevent this problem but than I can also swipe to remove outside the cell's visible height. This is caused by the tapLocation that is inside the collectionview and detects an indexPath. Something that I don't want. I want the swipe up only to work on a collectionview's cell.

Also the button and the image interfere with each other because I cannot distinguish them. They are both in the same cell so that's why I'm wondering if I should have the delete button in the cell at all. Or where should I place it otherwise? I could also make two buttons out of it and disable user interaction depending on state, but not sure how that would end up.

Answer

Michael picture Michael · Aug 9, 2016

So, if you want the swipes gesture recogniser to continue recording movement when they are outside of their collection view, you need to attach it to the parent of the collection view, so it's bounded to the full area where the user can swipe.

That does mean that you will get swipes for things outside the collection view, but you can quite easily ignore those using any number of techniques.

To register delete button taps, you'll need to call addTarget:action:forControlEvents: on the button

I would keep the cell as you have it, with the image and the button together. It will be much easier to manage, and they belong together.

To manage moving the image up and down, I would look at using a transform, or an NSLayoutConstraint. Then you just have to adjust one value to make it move up and down in sync with the user swipes. No messing with frames.