Creating a Fluid Scroll Experience on iOS

Ray Kim
Ray Kim
Jun 24 · 8 min read

Growing Pains

One of the main reasons we decided to redesign search was because our existing list UI was unable to adequately reflect the growing number of studios available. The list shows you available classes offered today and, depending on your device size, may only show a handful of classes at specific times above the fold. As the number of studios on ClassPass increased, we noticed that new users weren’t able to browse studios quickly. We have a map view accessible via a Map button at the top so that users can view studios geographically but user testing videos showed that most people simply didn’t know there was a button up there to switch to a map. As a result, users’ first impressions would be shaped by the small subset of classes they saw on their screen.


Apple Maps

First Implementation: Using a Container UIScrollView with a Content Inset

As I researched how to build this from scratch (it’s not a built-in UI component on iOS), Sanjay, another iOS engineer at ClassPass, recommended skagedal’s article on how to build an Apple Maps-like UI. It looked like the right approach–a UIScrollView with a custom contentInset with overrides on UIScrollViewDelegate methods to capture swipes and pan gestures. Our use case was a bit more complex because we weren’t simply dragging a list of directions like on Apple Maps–our scroll view would contain a segmented control component that switched between two types of lists, a UITableView for the studios list, and our existing classes list which lives inside a UIPageViewController. Despite our added complexity, I felt confident that I could apply this approach with our use case.

Apple Maps
Notice how it stops at the top and only works once the pan gesture finishes and a new one begins.

Second Implementation: Using a Container UIView with Custom UIPanGestureRecognizers

I started to feel like this would be as close as I could get given the amount of time I had to implement it. Other mobile engineers, Tom, and I weighed our options and despite everything else looking great, we all felt that it was important that we got this interaction right and that it was as smooth as possible before we shipped it to all of our users.

Continuous Scroll Part 1

Apple’s API for UIPanGestureRecognizer provides access to five pan gesture states: .began, .changed, .failed, .ended, and .cancelled. Using these five states, we can control when to only drag the container view (e.g., while the container hasn’t reached the top) and when to only drag its internal scroll view. To achieve the effect of dragging only the container view, you need two things. First, while the pan gesture is in the .changed state, update the container view’s frame to a calculated offset between the min and max vertical offsets you want for your draggable list (in our case we had to offset it from a header).

Capturing internal scroll view’s content offset inside ContainerViewController
Pass along scroll view through to parent view via delegate method

Continuous Scroll Part 2

Once we’ve reached the top, we want to stop updating the container view’s offset and start scrolling the internal scroll view. First, because we’ve set the internal scroll view’s contentOffset.y value to be 0 only for a specific range, the scroll view should scroll normally when not in that range. Second, in handlePan(_:), we check whether the internal scroll view’s contentOffset is greater than 0 (i.e., the internal scroll view is now scrolling) and return before calling the update frame code from earlier.

Conclusion

First, I’d like to say thanks to Tom and Sanjay for their guidance and support. Without them, it would’ve taken me a much longer time to get this feature right. I’d also like to thank my team for giving me the opportunity to work on such an impactful feature. From a mobile engineer’s perspective, there’s simply no other part of the app I could work on that has as much impact as working on improving the search experience. It’s by far the most important function of the mobile app and making it as polished and easy to use as possible is key to making it not only highly functional but also enjoyable to use. Thanks for reading!


ClassPass Engineering

We are the ClassPass engineering team

Thanks to Don Neufeld.

Ray Kim

Written by

Ray Kim

iOS Engineer @ClassPass. https://raykim.me

ClassPass Engineering

We are the ClassPass engineering team