If you’ve ever been using a UIScrollView and had a need to detect a single tap, I hear you. In the Savini Rims and Rides iPhone application, I had a horizontal scrollview of six cars as one element of the user interface. Selecting on a car (tapping once) was to launch a video. Trouble is, UIScrollView eats the single tap I was after.
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.
Comments
33 Responses to “Detect Single Tap in UIScrollView”
Leave Comment
All Content Copyright © 2008-2012 • iOS Developer Tips, All Rights Reserved.
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
[Reply]
John Muchow Reply:
December 10th, 2009 at 8:55 pm
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
[Reply]
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.
[Reply]
John Muchow Reply:
December 10th, 2009 at 9:28 pm
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?
[Reply]
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
[Reply]
John Muchow Reply:
December 10th, 2009 at 9:39 pm
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.
[Reply]
Hi John,
Thanks for pointing that out. That has fixed the error.
Thanks,
Kieran McGrady
[Reply]
Brilliant. Thank you for sharing. I had been playing around with a much less graceful solution!
[Reply]
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.
[Reply]
You just made my day! Thanks, Ralf
[Reply]
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).
[Reply]
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.
[Reply]
This tutorial is excellent! Save my days of works bouncing on the wall. Thanks.
[Reply]
excellent tutorial !!!
thanks so much
[Reply]
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???
[Reply]
Excellent tutorial….
[Reply]
Very nice tutorial ,Thank you very much!!!!
[Reply]
scrollView.userInteractionEnabled = YES; // put in the .m of your “SomeClass”
is the one thing I noticed was missing that DevSlashNull pointed out. thanks!
[Reply]
after 2 days of frustration, endless subclassing(subclassed UIView, UIImageView) I finally stumbled upon this article.
You Rock.
[Reply]
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:
[Reply]
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!
[Reply]
This worked perfectly for me with a slight modification, but thank you for the great tutorial nonetheless :)
[Reply]
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…
[Reply]
I like your tutorial, Very simple and you focus on core messages.
Good Job!!
[Reply]
many thanks UIScrollView dont eat my single click any more, nice work!!!
[Reply]
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!
[Reply]
You are mighty. After few hours of frustration and futile efforts I finally found this article. Well done, guys!
[Reply]
hi,
thanx for the info but I found its not working for sdk 5 any thoughts?
With my best,
Amir
[Reply]
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];
}
[Reply]
jerry Reply:
December 5th, 2011 at 6:18 pm
thank you Ed Rackham, i fixed touch event in iOS5.
now It works!
[Reply]
Tim Reply:
December 7th, 2011 at 8:46 pm
Thanks, Ed Rackham! It works!
[Reply]
Oza Klanjsek Reply:
January 4th, 2012 at 9:06 pm
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!
[Reply]
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.
[Reply]