The curious case of UIGestureRecognizers in scrollable views

The iOS app I've been working recently is heavy on displaying lists of stuff, particularly lists of stuff with tap actions in the cells/views. I chose to use UICollectionViews instead of UITableViews. Similar to a UITableViewController, UICollectionViewController has a method that you need to override in order to build each list item: GetCell().

I ran into a problem with making a RESTful call from a button tap inside each cell. This is done by adding a UITapGestureRecognizer to the button or UIView in each cell. Now, because each cell's contents are reconstructed every time that it scrolls onto the screen, if you do something like:

myButton.AddGestureRecognizer(new UITapGestureRecognizer(() => {
// make a RESTful call
}));

then the gesture recognizer will be added to the button each time the cell is scrolled back onto the screen.

So, what I was seeing is that if I scrolled the item off and then back onto the screen and then I tapped the button, the RESTful call was made TWICE instead of once. If I scroll it off and on the screen three times, the RESTful call fires THRICE! I only want it fire once!

What do do???

Solution
Execute the following while loop immediately before making the AddGestureRecognizer() call:

while (myButton.GestureRecognizers != null && myButton.GestureRecognizers.Length > 0)
{
     myButton.RemoveGestureRecognizer (myButton.GestureRecognizers [0]);
}
myButton.AddGestureRecognizer(new UITapGestureRecognizer(() => {
     // make a RESTful call
}));

This will programmatically remove all the gesture recognizers each time the cell is built. Problem solved!!!

Better yet...make an extension method to do this more conveniently:

public static class UIViewExtensionMethods {
    public static void RemoveAllGestureRecognizers(this UIView view) {
    while (view.GestureRecognizers != null && view.GestureRecognizers.Length > 0)
    view.RemoveGestureRecognizer(view.GestureRecognizers[0];
    }
}

...and call it like this:

myButton.RemoveAllGestureRecognizers();
myButton.AddGestureRecognizer(new UITapGestureRecognizer(() => {
// make a RESTful call
}));
comments powered by Disqus