Fixing Controls in Scroll Views on iOS

Buttons in scroll views have a subtly broken behavior. In this post, we show you a simple workaround to keep your UI feeling smooth and consistent with the design language of iOS.

You can download the sample project for this article if you’d like to try it for yourself.

Buttons on iOS change color when you press your finger down on them, which provides visual feedback to confirm which button will be activated when you lift your finger. This also gives you the chance to change your mind: If you drag your finger off the button, the touch will be canceled when you lift up, and this is indicated by the button’s un-highlighting.

A single, non-scrolling button. After pressing down on it, dragging outside the button and letting go cancels the touch.

A single, non-scrolling button. After pressing down on it, dragging outside the button and letting go cancels the touch.

Table and collection view cells behave similarly, in that they highlight when you press on them, and dragging cancels the tap. However, dragging also drags the table view. This is a nice touch, because table views are typically very responsive on iOS, and trying to scroll a table view that’s stuck can make an app feel broken.

Pressing a table view cell will highlight it, and dragging will cancel the touch and un-highlight the cell.

Pressing a table view cell will highlight it, and dragging will cancel the touch and un-highlight the cell.

Default table and collection view cells work like this out of the box, but if you add custom controls to your cells, or are adding controls to a UIScrollView directly, they will eat any touches that cause them to be highlighted, preventing those touches from reaching the scroll view. This, in turn, prevents the scroll view from scrolling if the user drags away from the button. You can see this behavior in the example on the left:

The normal scroll view, left, does not scroll if the button gets the touch first. The control-containable scroll view, right, does not have this problem, and therefore feels much more natural.

The normal scroll view, left, does not scroll if the button gets the touch first. The control-containable scroll view, right, does not have this problem, and therefore feels much more natural.

Fortunately, it’s easy to get the behavior pictured on the right. Just add these three classes to your project, and use them wherever you would normally use a UIScrollView, UITableView, or UICollectionView. The trick is overriding touchesShouldCancel(in:) to allow the scroll view to cancel touches in controls.

(Edit: added UISlider and UISwitch exceptions at @calebd’s suggestion.)

We use this trick in all our iOS apps here at Raizlabs. It’s the kind of thing users never notice, but, like smarter animated row deselection, it sands off a rough edge and removes a tiny bit of friction between your user and a great experience.


Do you have a project in mind? We’d love to work with you. If you’d like an opportunity to work on projects with us, check out our Careers page. We’re hiring!

Leave a Comment