Back to blog

Reproducing the iOS 7 Mail App’s Interface

iOS 7 introduced a whole new visual layer applied to its existing information architecture. One of the more interesting changes it made to the familiar gestures was how it augmented the swipe-to-delete gesture in the Mail app. The gesture for swiping a table view cell revealed not only a delete button, but also a “More” button that would open an action sheet with more options.

When I saw this in the WWDC keynote, I was excited. Hopefully, I thought, Apple would introduce this as a system-wide affordance so other apps could use this gesture to display custom buttons. I’m not able to comment on unreleased beta software, but I will say that Apple has a habit of using custom, private APIs that we don’t have access to. A shame for sure, but also an opportunity to build something fun.

This brings us to today’s tutorial. How can we recreate this interface? Since it’s a useful gesture to use now before iOS 7 is released, we’re going to be building against the iOS 6 SDK, but I’ve ensured that this works against the iOS 7 beta 4 APIs.

Based on the feel of the iOS 7 interface, I’d guess that Apple has implemented the swipe-to-show-options using a UIScrollView (something I can neither confirm nor deny based on inspection using Reveal). On that hunch, I’m going to implement our own version using a scroll view.

It’s important to note before going any further that we’re going to do all of our work inside the cell’s contentView, as per the documentation. Here’s what I’m picturing for the view hierarchy.

The contentView is going to contain a scroll view, which contains the buttons and other contents. We’ll set up our scroll view and add its subviews in our awakeFromNib method (or initWithStyle:reuseIdentifier:, as you prefer). We’re using kCatchWidth as a constant for how far the user has to drag before the scroll view “catches” and, when released, will show the buttons. It’s conveniently also equal to the combined width of both buttons.

We’re placing our buttons within their own view so that it’s easier to reposition. This is important because we need to counteract the scrolling behaviour to make the buttons appear to “stand still.” To achieve this effect, we’ll implement the scrollViewDidScroll: method.

This method also prevents the user from “pulling” the scroll view to the right, since we only want the swipe gesture to be accessible when pulling to the left. Next we’ll need to prevent the scroll view from coming to rest in between the states of showing the buttons and not showing them. To do this, we’ll implement another scroll view delegate method. This method is called when the scroll view is about to begin decelerating and it provides for us an opportunity to direct where the scroll view should come to rest.

This implementation forces the scroll view to come to rest just showing the buttons if it was pulled beyond the buttons, or returns it to the resting CGPointZero state if it hasn’t been pulled over far enough. Notice that we’re manipulating a pointer to a CGPoint and not a CGPoint itself. It’s also important to note that we have to manually call setContentOffset:animated: to return the scroll view to its default state. This is to workaround a strange flicker in the animation that I noticed in testing.

The next step is to register ourselves as a listener for a custom notification that indicates that the containing table view has been scrolled, and that we should reposition our scroll view to its default state of not showing the buttons.

The remainder of the implementation is very straightforward. A few delegate protocols here, an NSNotification there, and we’re done! It’s all documented in the code comments in the open source repository.

Happy coding!

Previous Posts in the iOS 7 series:

Ash Furrow More posts by Ash Furrow