Detect Single Tap in UIScrollView

Mon, Aug 17

If you’ve ever been using a UIScrollView and had a need to detect a single tap, let me show you what I came up with to catch single taps within a UIScrollView. First, I created a subclass of UIScrollView as follows:

Subclass UIScrollView

@interface AppScrollView : UIScrollView 
{
}
 
@end

The implementation of the AppScrollView is all of two methods, the initialization code and one method to manage touch events. The trick in detecting one touch is to look for the touchesEnded event, and within that method, check to see if the user is dragging when the event is triggered. If not dragging, pass the event up to the next responder – most likely, your class that contains an AppScrollView object.

AppScrollView Implementation

#import "AppScrollView.h"
 
@implementation AppScrollView
 
- (id)initWithFrame:(CGRect)frame 
{
  return [super initWithFrame:frame];
}
 
- (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event 
{	
  // If not dragging, send event to next responder
  if (!self.dragging) 
    [self.nextResponder touchesEnded: touches withEvent:event]; 
  else
    [super touchesEnded: touches withEvent: event];
}
 
@end

From here we can create a new class that includes a AppScrollView object:

Class Interface Using AppScrollView

#import <UIKit/UIKit.h>
 
@class AppScrollView;
 
@interface SomeClass : UIViewController <UIScrollViewDelegate>
{
  AppScrollView *scrollView;
  ...
}
 
@end

Now, inside the implementation of the SomeClass class, all we need to do is look for the touchesEnded event that was passed up the responder chain. For example, a skeleton of the class using AppScrollView may look as follows:

Class Implementation Receiving Single Tap

#import "AppScrollView.h"
 
@implementation SomeClass
 
...
 
- (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event 
{
  // Process the single tap here
  ...
}
 
...
 
@end

As with everything else, it’s easy once you know the trick.

36 comments

Hi, great tutorial. It is almost working for me. The method gets called but the code in my statement:

UITouch *touch = [touches anyObject];
if([touch view] == _scrollView )
{

does not get called. What view I am supposed to be detecting the touch on? Do I have to overlay a UIImageView on top of the scroll view and detect the touch on that?

Thanks

by Kieran McGrady on Dec 10, 2009. #

Kieran,

If I understand your question…generally you would have something inside your scroll view that you would be detecting a touch on, for example a series of images, which was the case for the project I was working on.

John

by John Muchow on Dec 10, 2009. #

Hi John ,

Thanks for the reply. I am displaying a scrollview which displays an image. When swiped the next image appears. I am using some sample code from apple to achieve this. I have a nib which has the image view and a class connected to that nib. I then have another class which controls the scrollview.

I create an instance variable to my class controlling the images :

SlidesViewController *sVC;

and tried:

if([touch view] == sVC.imageView )
{

But that failed with EXC_BAD_ACCESS.

by Kieran McGrady on Dec 10, 2009. #

Without seeing all the code it’s hard to know exactly where the problem is – are you sure the variable imageView is properly tied to the instance in the nib?

by John Muchow on Dec 10, 2009. #

Hi John,

In my SlidesViewController.h I define a UIImageView *imageView variable. This is hooked up correctly in the corresponding NIB. In my class that handles the scrollview I then have the following code:

- (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
// Process the single tap here
NSLog(@”Touch called”);
SlidesViewController *sVC;
//Create a UITouch object
UITouch *touch = [touches anyObject];
if(touch.view == sVC.imageView)
{

The app crashes with the error EXC_BAD_ACCESS when I attempt to single-tap and this code is run.

My code is pretty much identical to Apple page control sample code available here: http://developer.apple.com/iphone/library/samplecode/PageControl/index.html

by Kieran McGrady on Dec 10, 2009. #

I can see the problem for the bad access, if you look at the variable sVC, notice that you are referring to a new instance you just defined (SlidesViewController *sVC), therefore it points to nil. It looks like you need to refer to an instance variable with the name imageView.

by John Muchow on Dec 10, 2009. #

Hi John,

Thanks for pointing that out. That has fixed the error.

Thanks,
Kieran McGrady

by Kieran McGrady on Dec 10, 2009. #

Brilliant. Thank you for sharing. I had been playing around with a much less graceful solution!

by oldmicah on Mar 14, 2010. #

I have tried this, however without much success (because I am new and this may just be over my head). Is there anyone interested in posting a link to the actual code (including NIB files) as I am sure I am missing some elements when trying to implement the subview (uiscrollview still works but not seem to care about the tap I have placed using this method).

Thanks for any help you can provide…R.J.

by R.J. Hudecek on Mar 19, 2010. #

You just made my day! Thanks, Ralf

by Ralf Bernert on May 13, 2010. #

The “Apple approved” way of doing this is to subclass the view inside the UIScrollView and make sure its userInteraction property is set to YES.

Then implement touchesEnded on the subclassed view and do the appropriate action.

UIScrollView delays sending touches to the subviews until it determines that the touches don’t constitute a scroll action. If it has already sent the touches and they turn into a scroll it will send the touchesCanceled message (this behavior can be modified with the delaysContentTouches and canCancelContentTouches properties).

by DevSlashNull on May 27, 2010. #

Thanks again for this tutorial. I was able to get this working, however I am trying to use the touch event to change views (back to my main menu view controller, and have tried various methods however all of them have the similar result (with an warning error “AppScrollView may not respond to – presentModalViewController). (essentially my app has a main menu which opens my UIScrollView controller to show a gallery of images I want to tap to go back to the main menu). I can however open a URL so I know my subclassing and touch controls are correct. Any ideas would be greatly appreciated. Thanks.

by R.J. Hudecek on May 28, 2010. #

This tutorial is excellent! Save my days of works bouncing on the wall. Thanks.

by Kenny Chong on Jun 19, 2010. #

excellent tutorial !!!
thanks so much

by Manoj on Jun 28, 2010. #

Its a good tutorial!!!!!

I had a question I use to add a webview on subclassing scrollview and the problem is touches end is catched only after the userinteraction of webview is set NO…..

What I can do???

by Naveen Shan on Jul 6, 2010. #

Excellent tutorial….

by Mustafa Saify on Sep 8, 2010. #

Very nice tutorial ,Thank you very much!!!!

by Ali on Oct 25, 2010. #

scrollView.userInteractionEnabled = YES; // put in the .m of your “SomeClass”

is the one thing I noticed was missing that DevSlashNull pointed out. thanks!

by Shinebox on Oct 25, 2010. #

after 2 days of frustration, endless subclassing(subclassed UIView, UIImageView) I finally stumbled upon this article.
You Rock.

by Bharathn on Dec 14, 2010. #

This does not work for me. I’m trying to get touch events on the UIScrollView’s super view. No events are being given to touchesEnded:withEvent:

by Matthew Mitchell on Jan 5, 2011. #

Hey All,

Great Discussion! I’m really glad John started it. I had the same problem.

It seems I found a good method and example code from Apple. Check out “ScrollViewSuite.” The “1_TapToZoom” project shows you a super easy way to get at tap events stollen by the scrollview. I was able to get my plain-old view controller to implement this functionality with 4-5 lines of code and no subclassing!

by Charlie on Jan 23, 2011. #

This worked perfectly for me with a slight modification, but thank you for the great tutorial nonetheless :)

by Jason on Apr 4, 2011. #

thanks a million! I’ve used it grateful in test file. Though, I’m still trying to figure out why it don’t work on the tab bar application…

by chenhsien on Apr 13, 2011. #

I like your tutorial, Very simple and you focus on core messages.

Good Job!!

by Jeongho on Apr 26, 2011. #

many thanks UIScrollView dont eat my single click any more, nice work!!!

by hanoi_toulouse on Jun 14, 2011. #

any way to update the scrollview’s current offset in the super view?

i have a map with a rectangular slider that indicates the current position of the scrollview on the screen. i could update the position in the super view’s gameloop if i could get the position somehow. everything i’ve tried only updates the slider after any touch is finished.

thanks!

by skog on Jul 31, 2011. #

You are mighty. After few hours of frustration and futile efforts I finally found this article. Well done, guys!

by Piotr on Aug 6, 2011. #

hi,
thanx for the info but I found its not working for sdk 5 any thoughts?

With my best,
Amir

by Amir on Nov 1, 2011. #

Anyone else having problems with iOS5 / Xcode 4.2, you can fix it by adding the following to your UIScrollView subclass:

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.nextResponder touchesBegan: touches withEvent:event];
}

iOS5 / Xcode 4.2 seems to need to register the touchesBegan before it will allow touchesEnded to exist.

So your full UIScrollView subclass .m will contain the following:

- (id)initWithFrame:(CGRect)frame {
return [super initWithFrame:frame];
}

// This fixes it for iOS 5 / Xcode 4.2
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.nextResponder touchesBegan:touches withEvent:event];
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging){
[self.nextResponder touchesEnded:touches withEvent:event];
}
else
[super touchesEnded:touches withEvent:event];
}

by Ed Rackham on Nov 1, 2011. #

thank you Ed Rackham, i fixed touch event in iOS5.
now It works!

by jerry on Dec 5, 2011. #

Thanks, Ed Rackham! It works!

by Tim on Dec 7, 2011. #

Thank you very much!

I couldn’t believe that just a rebuild with Xcode 4.2 messed up this. This is a reasonable explanation. And now it works!

by Oza Klanjsek on Jan 4, 2012. #

Thanks a lot. iOS5 is getting my crazy with this kind of stuff.

It is the fifth problem I have to solve because of iOS 5. At least it has a razonable explanation.

Just to make any one know, because I didn’t find any explication.
I had an object which I need to change width and X position bounds. When I set new bounds, it changes X to -0. Crazy! I solved it setting bounds twice, first time for new X position, and second time for new Width.

First Code:
> bounds.size.width = bounds.size.width + 40;
> bounds.origin.x = ((bounds.size.width) * index;
> pagingScrollView.bounds = bounds;

Solution code:
> bounds.size.width = bounds.size.width + 40;
> pagingScrollView.bounds = bounds;
> bounds.origin.x = ((bounds.size.width) * index;
> pagingScrollView.bounds = bounds;

I hope it can help.

by Xavi on Nov 17, 2011. #

Thank you!!! ^^

I solved a problem.

But I must find long time the reason that did’nt operation.

I don’t know how change class identity. ^^;

by Kim sung jun on Aug 29, 2012. #

Can you please provide a little more information, I’m not sure what you mean by changing class identity?

by John Muchow on Aug 29, 2012. #

In someClass’s identity inspector. I had change superclass, UIScrollView => AppScrollView in custom class. Is Wrong???? ^^;;;

by Kim sung jun on Aug 29, 2012. #