UIScrollView prevents touchesBegan, touchesMoved, touchesEnded on view controller - iphone

I am handling touches for a couple of my UI components in my view controller (custom subclass of UIViewController). It has methods touchesBegan:withEvent:, touchesMoved:withEvent:, and touchesEnded:withEvent:. It was working fine. Then I added a scroll view (UIScrollView) as the top view in the hierarchy.
Now my touch handlers on the view controller don't work. They don't get called. The interesting thing is, I have various other UI components within the scroll view that do work. Some are buttons, some are custom views that define their own touchesBegan:withEvent:, etc. The only thing that doesn't work is the touch handlers on the view controller.
I thought maybe it's because the scroll view is intercepting those touches for its own purposes, but I subclassed UIScrollView and just to see if I could get it to work I am returning YES always from touchesShouldBegin:withEvent:inContentView: and NO always from touchesShouldCancelInContentView:. Still doesn't work.
If it makes a difference my view controller is within a tab bar controller, but I don't think it's relevant.
Has anyone had this problem and have a ready solution? My guess is the scroll view monkeys up the responder chain. Can I monkey it back? I guess if I can't figure anything else out I'll make the top level view under my scroll view be a custom view and forward the messages on to the view controller, but seems kludgy.

create a subclass of UIScrollView class and override the touchesBegan: and other touch methods as follows:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// If not dragging, send event to next responder
if (!self.dragging){
[self.nextResponder touchesBegan: touches withEvent:event];
}
else{
[super touchesBegan: touches withEvent: event];
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
// If not dragging, send event to next responder
if (!self.dragging){
[self.nextResponder touchesMoved: touches withEvent:event];
}
else{
[super touchesMoved: touches withEvent: event];
}
}
-(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];
}
}

Well this worked, but I'm not sure I can "get away with it", since nextResponder is not one of the UIView methods you're "encouraged" to override in a subclass.
#interface ResponderRedirectingView : UIView {
IBOutlet UIResponder *newNextResponder;
}
#property (nonatomic, assign) IBOutlet UIResponder *newNextResponder;
#end
#implementation ResponderRedirectingView
#synthesize newNextResponder;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (UIResponder *)nextResponder {
return self.newNextResponder;
}
- (void)dealloc
{
[super dealloc];
}
#end
Then in Interface Builder I made the direct subview of the scroll view one of these, and hooked up its newNextResponder to skip the scrollview and point directly to the view controller.
This works too, replacing the override of nextResponder with these overrides:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.newNextResponder touchesBegan:touches withEvent:event];
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[self.newNextResponder touchesMoved:touches withEvent:event];
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self.newNextResponder touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self.newNextResponder touchesCancelled:touches withEvent:event];
}

"It was working fine. Then I added a scroll view (UIScrollView) as the top view in the hierarchy."
is your scrollview on top of your contentview that contains items?
all your components should be in the scrollview and not the view behind the scrollview

user1085093's answer worked for me. Once you move the touch more than a small amount, however, it then gets interpreted as a Pan Gesture.
I overcame this by altering the behaviour of the Pan Gesture recogniser so it requires two fingers:
-(void)awakeFromNib
{
NSArray *gestureRecognizers = self.gestureRecognizers;
UIGestureRecognizer *gestureRecognizer;
for (gestureRecognizer in gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *pgRecognizer = (UIPanGestureRecognizer*)gestureRecognizer;
pgRecognizer.minimumNumberOfTouches = 2;
}
}
}

The touchesBegan: etc methods will NEVER be called in a UIScrollView because it is a subclass of UIView and overrides these methods. check out the different UIScrollView methods available here. The work-around will depend on what you want to implement.

Related

Detect touch on uiview under the UIButton when button is pressed

i am using a uiview parent view....overidded touch began and touch ended methods...
when i added a UIButton as a subview in the view and touch on the button touchdown event is detected and associated method is called ...but touchbegan method that was over ridden is not called...
what i want is when i touch the button the method associated with the touchdown event and touchBegan method of uiview both be called simultaneously...UIbutton is not passing the touch to its superview i.e. uiview.....?
Any idea how to do that ?
Thanks
I'm not sure exactly how to call two touchesBegan events simultaneously on two different views, but you probably want to override the hitTest:withEvent: method of UIView. This will allow you to catch a touch event on the view underneath the UIButton before it gets to the button (hitTests work from the window upwards to the foremost view).
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (passToSubviews)
{
return [super hitTest: point withEvent: event];
}
// otherwise stop the subview receiving touches
if ([super hitTest: point withEvent: event])
{
return self;
}
return nil;
}
Maybe this can help...
EDIT:
Just a guess but you could try:
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* hitSubview = [super hitTest: point withEvent: event];
if (hitSubview)
{
[self doTouchedInStuff];
return hitSubview;
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self doTouchedInStuff];
[super touchesBegan:touches withEvent:event];
}

How can I detect a touch up inside in my custom view?

I've created a subclass of UIScrollView to implement a one of my custom controls, at this point everything is working great.
However what I'd like to be able to call a methods whenever a Touch Up Inside event is detected (just like interface builder) does anybody know how I could do this?
Because UIScrollView does not inherit from UIControl, this is not possible. You can, however, relay the scroll view's touch events by implementing the UIResponder methods in your custom UIScrollView class:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (!self.dragging)
{
[self.nextResponder touchesEnded: touches withEvent:event];
}
[super touchesEnded: touches withEvent: event];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (!self.dragging)
{
[self.nextResponder touchesBegan: touches withEvent:event];
}
[super touchesBegan: touches withEvent: event];
}

Parent UIScrollView detecting child UIView's touches

Basically...
I have a UIScrollView subclass which you can add a target and selector too, which get fired if a touch event is detected within the view. I am using the scroll view as an image gallery, and the touch inside the scroll view is used to fade out the HUD components (UItoolBar, etc):
#implementation TouchableScrollView
- (void)addTarget:(id)_target withAction:(SEL)_action
{
target = _target;
action = _action;
shouldRun = NO;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
shouldRun = YES;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
shouldRun = NO;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if(shouldRun)
{
shouldRun = NO;
[target performSelector:action];
}
}
#end
I then have another custom UIView added as a subview to this which has the following set (both of which I have played around with):
self.userInteractionEnabled = YES;
self.exclusiveTouch = YES;
and uses the following to trigger an animation:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
The problem is even when the custom UIView detects the touch and responds (by flipping itself over), the UIScrollView selector also gets fired causing everything to fade out.
Please help, I'm totally stuck?!
better to use this in custom ScrollView class
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
if (!self.dragging) {
[self.nextResponder touchesEnded: touches withEvent:event];
}
[super touchesEnded: touches withEvent: event];
self.nextResponder is scrollView subViews. so event other then dragging (scrolling) will be handled by your main viewController . in main View controller inherit touch event method. there u can check for touch event for particular view or image view.

iPhone - how to track touches and allow button taps at the same time?

I'm wondering how to track touches anywhere on the iPhone screen and still have UIButtons respond to taps.
I subclassed a UIView, made it full screen and the highest view in the hierarchy, and overrode its pointInside:withEvent method. If I return YES, I'm able to track touches anywhere on the screen but the buttons don't respond (likely because the view is instructed to handle and terminate the touch). If I return NO, the touch passes through the view and the buttons respond, but I'm not able to track touches.
Do I need to subclass UIButton or is this possible through the responder chain? What am I doing wrong?
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
return NO;
}
//only works if pointInside:withEvent: returns YES.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"began");
[self.nextResponder touchesBegan:touches withEvent:event];
}
//only works if pointInside:withEvent: returns YES.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(#"end");
[self.nextResponder touchesEnded:touches withEvent:event];
}
Rather then having extra view you can subclass your applications main window and track touches (and other events in it)
#interface MyAppWindow : UIWindow
...
#implementation MyAppWindow
- (void)sendEvent:(UIEvent*)event {
[super sendEvent:event];
if (event.type == UIEventTypeTouches){
// Do something here
}
return;
}
Then set your application window type to MyAppWindow (I did that in MainWindow.xib in IB)

UIScrollview+UItextview touch event

i am using a scroll view and that scroll view i have UIIMageview and UITextview.My Question is -i want the Touchevent of Textview.
I think UIScrollView becomes the first responder of touch events, and it doesn't pass the touch events to other view as a default.So, why don't you create a subclass of the UIScrollView? Then, pass the all touch events which are received by the subclass to the UIIMageview or UITextview like as follows.
#implementation SubclassOfScrollView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.nextResponder touchesBegan:touches withEvent:event];
}
#end