I’ve been diving into iOS for the past month building an app for my startup. One of the things I want the app to do is to let the user swipe a cell in a table view to reveal more options (similar to how the Mail app reveals “More” and “Delete” options). In this article I will discuss a few way of doing this, including how to build it yourself.
First, let’s go through the existing solutions out there:
- UITableViewRowAction (new in iOS 8)
This option is the best if you’re just looking to add a few buttons that appear when swiping right-to-left on the table view cell.
A full-featured implementation of buttons for both right-to-left swipe and left-to-right swipe. This will work for older iOS versions too.
Another full-featured implementation, with even more functionality.
Hopefully, if you’re looking for swipeable table view cells, you’ll find what you need in one of the above solutions. But then why am I writing about how to build it yourself? In my case, I needed some of the functionality of MGSwipeTableCell, but it turned out to do things in ways that didn’t work for me. If you’re also looking to build it from scratch, or just want to know how it works, read on!
Anatomy of a swipeable UITableViewCell
Making a table view cell swipeable is actually pretty simple. The gist of it is to create a UIScrollView inside the cell’s contentView and then populate it with one UIView for the buttons and one UIView for the content. The hardest part is really configuring the scroll view. Here’s an image showing the main values we’ll be touching on the UIScrollView:
The size of the view (the actual area that will be rendered on screen). In the picture above I gave it more height to make room for the labels, but in our code it will have the same height as the content.
In scroll views, the content will often be a different size than the view itself. When content is bigger than the view, the view will scroll to let the user see all of the content. This property represents the width and height of the content inside of the scroll view. In our case, we’ll actually keep it the same size as the view, thanks to a property called contentInset.
This is the property that will make our scroll view actually scroll. Normally, when the contentSize property is set to the same (or smaller) size as the view itself, the view won’t scroll. However, contentInset will extend the area that is scrollable outside the content, so that you can scroll past the content boundary. Why is this useful? It’s used by pull-to-refresh controls, for example, to allow a throbber to show above the content. In our case, we’ll use it to make room for revealing the buttons.
This is the value that represents how much the scroll view is currently scrolled. As the user scrolls around, this value will change. You can also change it programmatically, which we’ll do later to snap the scrolling to just where the buttons end.
Implementing the cell, step by step
This section will demonstrate how to actually make the cell display buttons when swiping on it. The code examples will be simplified to focus on the concept. At the end I will provide a full implementation.
First, let’s subclass UITableViewCell to begin implementing our swipeable table cell.
Since we’ll want to have control over scroll behaviors, we need to specify that our class supports the UIScrollViewDelegate protocol. Do this by adding the protocol in angle brackets after the interface declaration:
Next up, we’ll set up the scroll view. This is done when the cell initializes. Depending on how you create your cells, the cell may initialize in either initWithCoder: or initWithStyle:reuseIdentifier:. I would recommend creating a common method for setting up your cell and calling it from both places.
There’s a few things happening here after we create the scroll view. First, we set its resizing mask. This means how the view will act when its super view changes size. We want the scroll view to fill the entire cell when it changes size at runtime (which is likely to happen with iPhone 5, 6 and 6 Plus all having different screen widths). Then we set the content size to be the same, but also add a left inset with the width of the buttons we intend to add. Setting scrollsToTop to be turned off means this scroll view doesn’t care about the status bar being tapped (which normally scrolls a view to the top). This’ll let our table view take care of that instead of breaking that behavior. We also turn off horizontal and vertical scroll indicators so that the little translucent black bar doesn’t appear when we scroll.
Nothing really surprising going on above, we’re just adding in the necessary parts for presenting the main area of the table cell (which is normally in the contentView).
This creates a view which contains the action buttons. The important things to note here is that the view has the same width as all the buttons together, and it’s inserted as the first view in the hierarchy (which will make it appear under everything else). Then we just add the buttons we want there. I didn’t add tap handlers to keep the example short.
If you run the app now, it should let you scroll to make the button appear. If you’re running this on an iPhone 6 or larger you may notice a glitch that we’ll fix later. Another thing missing is the snapping in place of the button being either visible or not visible when you stop scrolling. Let’s fix that now.
When your class gets notified that the user has stopped swiping (via the UIScrollViewDelegate protocol), you make the scroll view scroll back to either exactly where the buttons end (if they swiped past the buttons), or back to hide the buttons completely (if they didn’t swipe far enough).
That’s pretty much the basics of building a swipeable cell! We’ve got a few loose ends that we’ll tie up now, so keep reading.
The biggest problem with our code right now is handling of bigger screens (such as iPhone 6). We can fix that with this simple “hack” (please let me know if this can be done better!)
That’ll avoid graphical glitches. Next up, we may want to prevent the cell from being scrollable in the other direction (which doesn’t have any buttons to show). That’s simple:
Finally, you may want the buttons to stay in place under the cell instead of moving with the cell content. To make that happen, we will simply move the buttons container view to counteract the scrolling offset. We’ll do this by changing the scrollViewDidScroll: method above to the following:
Where to go from here
Now that we’ve gone through the basics there’s many things that can still be improved. Here’s a few:
- Auto-hiding buttons when scrolling the table view
- Auto-hiding buttons when swiping open another cell
- Allowing buttons on both sides of the cell
- Enabling button creation outside the class (e.g., in the view controller)
- Dynamically calculating the buttons container size
- Making opening/closing of the actions menu behave better
To fast-forward a bit, I’ve implemented the features above and made the code available on GitHub: https://github.com/blixt/SwipeableTableViewCell
Thank you for reading! Please reach out to me with any comments. The easiest way to reach me is on Twitter, where I’m known as @blixt.