have spent an age searching for the answer to this problem and found a number of references to the solution, i.e subclassing the UIScrollView class, but no examples.
I found a lot of people asking the same question as me (or rather I was asking same as them lol)
So I created an app from scratch and thought I would post the detail here for others to use. learn or flame at bad coding....but it works for me so I am happy!
OK..here goes:
Start a new project called ScrollViewTapDetectionViewController of class UIViewController.
Here is the code, read my comments...
Code:
#import
// these will be covered in a flie to come later in this tutorial :)
@class myScrollView;
@class myImageView;
// make sure you set this to be a UIScrollViewDelegate!
@interface ScrollViewTapDetectionViewController : UIViewController {
// this will be our UIScrollView subclass
myScrollView *contentView;
// this will be our UIImageView subclass
myImageView *imageView;
}
@property (retain, nonatomic) myScrollView *contentView;
@property (retain, nonatomic) myImageView *imageView;
@end
Now create another class of UIViewController and name it myScrollView
here is the code, again read my comments...
Code:
#import
// change the class to be of type UIScrollView that is all for this file :)
@interface myScrollView : UIScrollView {
}
@end
And again add another class of type UIViewController
here is the code, again read my comments...
Code:
#import
// change the class to be of type UIImageView that is all for this file :)
@interface myImageView : UIImageView {
}
@end
Now open the file myScrollView.m and ad this code, read my comments ;-)
Code:
#import "myScrollView.h"
@implementation myScrollView
// This code is from ThirtyOne's post.....thanks a million TO ;-)
-(void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
if (!self.dragging) {
[self.nextResponder touchesEnded: touches withEvent:event];
}
[super touchesEnded: touches withEvent: event];
}
Now open the file myImageView.m and add this code ;-)
Code:
#import "myImageView.h"
@implementation myImageView
// simple method you should be familiar with!
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
NSLog(@"Touch detected");
}
Now for the nitty gritty to pull it all together
Code:
// import the required header files
#import "ScrollViewTapDetectionViewController.h"
#import "myScrollView.h"
#import "myImageView.h"
@implementation ScrollViewTapDetectionViewController
@synthesize contentView;
@synthesize imageView;
// we want the instance of our myImageView to be the one used for scrolling
-(UIView *) viewForZoomingInScrollView: (UIScrollView *) ScrollView
{
return imageView;
}
-(void) loadView
{
// set the image to be displayed, pic your own image here
imageView = [[myImageView alloc] initWithImage: [UIImage imageNamed: @"AnyOldImage.png"]];
// yes we want to allow user interaction
[imageView setUserInteractionEnabled:YES];
// set the instance of our myScrollView to use the main screen
contentView = [[myScrollView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
// turn on scrolling
[contentView setScrollEnabled: YES];
// set the content size to the size of the image
[contentView setContentSize: imageView.image.size];
// add the instance of our myImageView class to the content view
[contentView addSubview: imageView];
// flush the item
[imageView release];
// set max zoom to what suits you
[contentView setMaximumZoomScale:1.0f];
// set min zoom to what suits you
[contentView setMinimumZoomScale:0.25f];
// set the delegate
[contentView setDelegate: self];
// scroll a portion of image into view (my image is very big) :)
[contentView scrollRectToVisible:CGRectMake(400, 400, 320, 440) animated:NO];
// yes to autoresize
contentView.autoresizesSubviews = YES;
// set the mask
contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
// set the view
self.view =contentView;
}
Now compile and run your app and ensure you have the console open.
Drag the image around, zoom in and out etc and nothing appears in the console which is what we want. But not just click on the image and hey presto.......the text Touch detected appears
hope this helps someone
NB I have not entered the code for release instance variables etc....you can do that ;-)
Thanks for sharing your experience! I think this thread would be better served in the Tutorials forum. Would you like me to move it there for others to view and learn from? In return, I can make you a Tutorial Author on the site which will allow you to create threads in the Tutorials forum in the future.
I had to check UserInteractionEnabled for the contentView, MyImageView, and the UIButton.
Note however that I did not have to use ThirtyOne's code. I did not have to subclass UIScrollView. It seems as though the documentation is telling the truth in that UIScrollView passes touches to the subviews by default. (Look for touchesShouldBegin in the documentation).
Also note that touches are passed immediately when zoomed all the way out (no scrolling possible). But when zoomed in some, touches only pass after a short delay (to make sure the touch isn't a scroll).
I made this with IB in 10 minutes. I am trying to get this to work using view controllers and programming my view hierarchy manually, but have not been successful yet.
The forum limit for attachments is much smaller than the file. Anyone know where I can put it? Or you can just pm me or e-mail me if you want me to send it to you.
Hi, I'm Sean. I think it sounds great and i'd love to see how you did it. I'm actually faced with exactly this situation and posted a Question on this to the forums. I basically built a scrollView and loaded it with buttons horizontally. 33 to be exact. Now i need to trap the (id)Sender information or the buttons, but can't figure out how to link them to an IBAction (one function for all), must less a firstResponder's IBAction ... where my global actions reside.
Hi, I'm Sean. I think it sounds great and i'd love to see how you did it. I'm actually faced with exactly this situation and posted a Question on this to the forums. I basically built a scrollView and loaded it with buttons horizontally. 33 to be exact. Now i need to trap the (id)Sender information or the buttons, but can't figure out how to link them to an IBAction (one function for all), must less a firstResponder's IBAction ... where my global actions reside.
Thanks! for any help.
You can link all the buttons to the same method in your code from IB.
You can do the same if creating the button progmatically using addTarget.
Lastly you could also add a unique tag to each button and query that in your single method.
I am so happy that I found your post! I have been struggling with this issue for quite some time now, and who would have thought that all I had to do was just sub-class the UIImageView and the UIScrollView. Woohoo!
It made me decide to become a subscriber to iPhone Dev SDK.
I might be missing something, but i'd suggest just adding a Label on top of your image and (through some iteration) setting the Label.text as appropriate each time through the loop.
I basically solved my problem above by:
1) create my content UIView
2) load my girls from a Plist into an NSDictionary
3) start a loop which, for each girl in list, determines image(s) based on whether the user has clicked the button before or not ... and sets the labels appropriately, and calculates the position on the scroll view for the button, and passes all this to another method (createButtonForGirl) which actually creates the button, loads it onto the view and returns to the loop event ... repeating this step until all are loaded =) and then (finally) once completed we load the SwipeView with the ContentView and then addSubview:SwipeView =)
My first product is called uOrgasm (www.drummerboy.me) and is waiting approval from Apple. =) chow and Good Luck !!!!
Last edited by seanrice; 06-17-2009 at 01:19 PM.
Reason: clarification
have spent an age searching for the answer to this problem and found a number of references to the solution, i.e subclassing the UIScrollView class, but no examples.
I found a lot of people asking the same question as me (or rather I was asking same as them lol)
So I created an app from scratch and thought I would post the detail here for others to use. learn or flame at bad coding....but it works for me so I am happy!
OK..here goes:
Start a new project called ScrollViewTapDetectionViewController of class UIViewController.
Here is the code, read my comments...
Code:
#import
// these will be covered in a flie to come later in this tutorial :)
@class myScrollView;
@class myImageView;
// make sure you set this to be a UIScrollViewDelegate!
@interface ScrollViewTapDetectionViewController : UIViewController {
// this will be our UIScrollView subclass
myScrollView *contentView;
// this will be our UIImageView subclass
myImageView *imageView;
}
@property (retain, nonatomic) myScrollView *contentView;
@property (retain, nonatomic) myImageView *imageView;
@end
Now create another class of UIViewController and name it myScrollView
here is the code, again read my comments...
Code:
#import
// change the class to be of type UIScrollView that is all for this file :)
@interface myScrollView : UIScrollView {
}
@end
And again add another class of type UIViewController
here is the code, again read my comments...
Code:
#import
// change the class to be of type UIImageView that is all for this file :)
@interface myImageView : UIImageView {
}
@end
Now open the file myScrollView.m and ad this code, read my comments ;-)
Code:
#import "myScrollView.h"
@implementation myScrollView
// This code is from ThirtyOne's post.....thanks a million TO ;-)
-(void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
if (!self.dragging) {
[self.nextResponder touchesEnded: touches withEvent:event];
}
[super touchesEnded: touches withEvent: event];
}
Now open the file myImageView.m and add this code ;-)
Code:
#import "myImageView.h"
@implementation myImageView
// simple method you should be familiar with!
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
NSLog(@"Touch detected");
}
Now for the nitty gritty to pull it all together
Code:
// import the required header files
#import "ScrollViewTapDetectionViewController.h"
#import "myScrollView.h"
#import "myImageView.h"
@implementation ScrollViewTapDetectionViewController
@synthesize contentView;
@synthesize imageView;
// we want the instance of our myImageView to be the one used for scrolling
-(UIView *) viewForZoomingInScrollView: (UIScrollView *) ScrollView
{
return imageView;
}
-(void) loadView
{
// set the image to be displayed, pic your own image here
imageView = [[myImageView alloc] initWithImage: [UIImage imageNamed: @"AnyOldImage.png"]];
// yes we want to allow user interaction
[imageView setUserInteractionEnabled:YES];
// set the instance of our myScrollView to use the main screen
contentView = [[myScrollView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
// turn on scrolling
[contentView setScrollEnabled: YES];
// set the content size to the size of the image
[contentView setContentSize: imageView.image.size];
// add the instance of our myImageView class to the content view
[contentView addSubview: imageView];
// flush the item
[imageView release];
// set max zoom to what suits you
[contentView setMaximumZoomScale:1.0f];
// set min zoom to what suits you
[contentView setMinimumZoomScale:0.25f];
// set the delegate
[contentView setDelegate: self];
// scroll a portion of image into view (my image is very big) :)
[contentView scrollRectToVisible:CGRectMake(400, 400, 320, 440) animated:NO];
// yes to autoresize
contentView.autoresizesSubviews = YES;
// set the mask
contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
// set the view
self.view =contentView;
}
Now compile and run your app and ensure you have the console open.
Drag the image around, zoom in and out etc and nothing appears in the console which is what we want. But not just click on the image and hey presto.......the text Touch detected appears
hope this helps someone
NB I have not entered the code for release instance variables etc....you can do that ;-)
HI,
Nice post
You could have shared that application. It will be more helpful
have spent an age searching for the answer to this problem and found a number of references to the solution, i.e subclassing the UIScrollView class, but no examples.
I found a lot of people asking the same question as me (or rather I was asking same as them lol)
So I created an app from scratch and thought I would post the detail here for others to use. learn or flame at bad coding....but it works for me so I am happy!
OK..here goes:
Start a new project called ScrollViewTapDetectionViewController of class UIViewController.
Here is the code, read my comments...
Code:
#import
// these will be covered in a flie to come later in this tutorial :)
@class myScrollView;
@class myImageView;
// make sure you set this to be a UIScrollViewDelegate!
@interface ScrollViewTapDetectionViewController : UIViewController {
// this will be our UIScrollView subclass
myScrollView *contentView;
// this will be our UIImageView subclass
myImageView *imageView;
}
@property (retain, nonatomic) myScrollView *contentView;
@property (retain, nonatomic) myImageView *imageView;
@end
Now create another class of UIViewController and name it myScrollView
here is the code, again read my comments...
Code:
#import
// change the class to be of type UIScrollView that is all for this file :)
@interface myScrollView : UIScrollView {
}
@end
And again add another class of type UIViewController
here is the code, again read my comments...
Code:
#import
// change the class to be of type UIImageView that is all for this file :)
@interface myImageView : UIImageView {
}
@end
Now open the file myScrollView.m and ad this code, read my comments ;-)
Code:
#import "myScrollView.h"
@implementation myScrollView
// This code is from ThirtyOne's post.....thanks a million TO ;-)
-(void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
if (!self.dragging) {
[self.nextResponder touchesEnded: touches withEvent:event];
}
[super touchesEnded: touches withEvent: event];
}
Now open the file myImageView.m and add this code ;-)
Code:
#import "myImageView.h"
@implementation myImageView
// simple method you should be familiar with!
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
NSLog(@"Touch detected");
}
Now for the nitty gritty to pull it all together
Code:
// import the required header files
#import "ScrollViewTapDetectionViewController.h"
#import "myScrollView.h"
#import "myImageView.h"
@implementation ScrollViewTapDetectionViewController
@synthesize contentView;
@synthesize imageView;
// we want the instance of our myImageView to be the one used for scrolling
-(UIView *) viewForZoomingInScrollView: (UIScrollView *) ScrollView
{
return imageView;
}
-(void) loadView
{
// set the image to be displayed, pic your own image here
imageView = [[myImageView alloc] initWithImage: [UIImage imageNamed: @"AnyOldImage.png"]];
// yes we want to allow user interaction
[imageView setUserInteractionEnabled:YES];
// set the instance of our myScrollView to use the main screen
contentView = [[myScrollView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
// turn on scrolling
[contentView setScrollEnabled: YES];
// set the content size to the size of the image
[contentView setContentSize: imageView.image.size];
// add the instance of our myImageView class to the content view
[contentView addSubview: imageView];
// flush the item
[imageView release];
// set max zoom to what suits you
[contentView setMaximumZoomScale:1.0f];
// set min zoom to what suits you
[contentView setMinimumZoomScale:0.25f];
// set the delegate
[contentView setDelegate: self];
// scroll a portion of image into view (my image is very big) :)
[contentView scrollRectToVisible:CGRectMake(400, 400, 320, 440) animated:NO];
// yes to autoresize
contentView.autoresizesSubviews = YES;
// set the mask
contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
// set the view
self.view =contentView;
}
Now compile and run your app and ensure you have the console open.
Drag the image around, zoom in and out etc and nothing appears in the console which is what we want. But not just click on the image and hey presto.......the text Touch detected appears
hope this helps someone
NB I have not entered the code for release instance variables etc....you can do that ;-)
I tried. but it seems the imageview still cannot reponse the multi-touch message?
in 3.0 sdk this is not working. I think this is the issue
Issue: Touches are no longer forwarded to a UIScrollView in 3.0.
Calling touchesBegan/touchesMoved/touchesEnded/touchesCancelled methods directly is not supported. Previously, all touches in subviews of a UIScrollView were hit tested to the UIScrollView itself, which then attempted to forward the touches to the correct content view. This often resulted in the UIScrollView having its touchesBegan/touchesMoved/touchesEnded/touchesCancelled methods called twice for each event. In 3.0 this is no longer the case, and UIScrollView now behaves the same as every other UIKit-provided view. Whichever content view was actually touched is returned from hitTest, and if that view implements the UIResponder touch methods and doesn't call super, then the responder methods for UIScrollView will never get called. Instead, use touchesShouldBegin:withEvent:inContentView: or touchesShouldCancelInContentView:.
Issue: Touches are no longer forwarded to a UIScrollView in 3.0. ... how can we solve that?
The answer is, you must handle touch events in the subview and modify the behavior of the scroll view by setting its canCancelContentTouches and delaysContentTouches flags appropriately.
When delaysContentTouches is set to YES, no event is sent to your subView until it can be determined that it is NOT a scroll gesture. Thus this flag always causes a delay (unless you tap very quickly).
With delaysContentTouches set to NO, the event is sent immediately to your subview. If canCancelContentTouches is set to YES then any gesture that is "scrolly enough" will (by default) cancel events to your subview and initiate scrolling. If canCancelContentTouches is set to NO then no scrolling will occur, no matter what gesture occurs.
With canCancelContentTouches set to YES you can use the touchesShouldCancelInContentView method in your UIScrollView subclass to allow or disallow scrolling based on whatever criteria you want. For example, if you only want to allow scrolling when a finger swipe is mostly vertical, but not if it's at or under 45°, you can test for this in your subView's touch processing and set the value of a BOOL variable that touchesShouldCancelInContentView can return.
This should work in theory, though I haven't fully implemented it yet. It may happen that a swipe at 10° will cause scrolling to start before the subView can determine that the angle is too oblique (or too obtuse, for that matter).
The delaysContentTouches flag causes the scrollView to start a timer when it's first touched, and when the timer runs out it either scrolls its contents or starts streaming events to the subview. (A sufficiently scrolly gesture will also cancel the timer and start scrolling.) Unfortunately there's no documented way to change the duration of the timer or to implement your own scroll-worthy test in your UIScrollView subclass, so the only option seems to be what I've outlined.
__________________
|
| I wrote ChordCalc ... A cool fretboard calculator.
|
hakimny,
This doens't work for me as the touchesEnded doesn't even get invoked. The only way for me to access the touch detection is via the hittest method, and then I run in everything else except touches event freezing
Your code in hitTest isn't doing anything useful, so you might as well drop that. The property you're setting there is sticky, you can just set it on your scroll view and leave it set.
However, you DON'T want to set delaysContentTouches to YES, because it will only allow touch-up events to be detected in your subviews. That's why your web view can only receive simple taps, and not swipes.
Instead, set canCancelContentTouches=YES and delaysContentTouches=NO, then implement -touchesShouldCancelInContentView: on your scrollview. This is called the moment you do anything that could scroll the view. You should return YES if you want to allow the scroll to happen, and NO if you want to prevent it.
Since you have subviews inside your scrollview that want to receive swipes and things, you can simply set a BOOL flag (e.g., a global var or a property in your scrollview) to NO whenever one of those subviews receives any touch event, then return that flag from touchesShouldCancelInContentView:. When your subview is done ( receives -touchesEndedWithEvent: ) reset the flag to YES to reallow scrolling.
Either way, touchesShouldCancelInContentView: is the key.
__________________
|
| I wrote ChordCalc ... A cool fretboard calculator.
|