Swipeable collection view cell - a simpler approach


In one of my recent projects, I had to implement a feature where the user could swipe a cell to show a hidden delete button. Since iOS 11, Apple introduced some nice improvements in the UITableView class - specifically a better support for displaying hidden menu items when the user swipes a UITableViewCell. You can implement delegate methods for displaying leading and trailing swipe actions and that would be enough. Unfortunately, I couldn’t use that API because I had to use a UICollectionView.
I wanted to avoid using third-party libraries for such a relatively simple task, so I came up with my custom solution. I’ve set up an example project on GitHub that shows how you can achieve the effect shown in the image below:

Swipe to show delete

Implementation

I’ve experimented with multiple ways of implementing this feature. I found that the most concise and clean solution for me was by using UIScrollView in combination with UIStackView.

class SwipeableCollectionViewCell: UICollectionViewCell {

    private let scrollView: UIScrollView = {
        let scrollView = UIScrollView(frame: .zero)
        scrollView.isPagingEnabled = true
        scrollView.showsVerticalScrollIndicator = false
        scrollView.showsHorizontalScrollIndicator = false
        return scrollView
    }()

    let visibleContainerView = UIView()
    let hiddenContainerView = UIView()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupSubviews()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupSubviews() {
        // 1
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.distribution = .fillEqually
        stackView.addArrangedSubview(visibleContainerView)
        stackView.addArrangedSubview(hiddenContainerView)

        // 2
        addSubview(scrollView)
        scrollView.pinEdgesToSuperView()
        scrollView.addSubview(stackView)
        stackView.pinEdgesToSuperView()
        stackView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
        stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 2).isActive = true
    }

}


  1. First, we need to create 2 container views - one for the visible part of the cell and one for the hidden part. We’ll add these views to a UIStackView. The stackView will act as a content view and make sure that the views have equal widths with the distribution property set to .fillEqually.

  2. We’ll embed the stackView inside a scrollView that has paging enabled. The scrollView is constrained to the edges of the cell. Since we’ve set the stackView’s width to be 2 times the scrollView’s width - each of the container views will have the width of the cell.

With this simple implementation, we have created the base cell with a visible and hidden view.

Recognizing taps

We’ll need to add a way to recognize if the user tapped the hidden view. We’ll create SwipeableCollectionViewCellDelegate protocol that we can adopt so that we can respond to tap events.

protocol SwipeableCollectionViewCellDelegate: class {
    func hiddenContainerViewTapped(inCell cell: UICollectionViewCell)
}

Now we just need to set up a gesture recognizer:

private func setupGestureRecognizer() {
    let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(hiddenContainerViewTapped))
    hiddenContainerView.addGestureRecognizer(tapGestureRecognizer)
}

@objc private func hiddenContainerViewTapped() {
    delegate?.hiddenContainerViewTapped(inCell: self)
}


Conclusion

In this blog post, we’ve seen one way of implementing the swipe feature on a collection view cell. I think that the biggest advantage of this solution is its simplicity and customisability. The code is very easy to understand. There was no need to add additional pan gesture recognizers and implement custom paging since that is solved by the UIScrollView.