UIScrollView prevents touchesBegan, touchesMoved, touchesEnded on view controller
Asked Answered
R

5

26

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.

Ruthannruthanne answered 16/9, 2011 at 1:32 Comment(1)
Follow my answer here https://mcmap.net/q/372080/-uiscrollview-touchesbegan/…Zaid
C
22

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];
   }
}
Countable answered 30/1, 2012 at 9:16 Comment(2)
Thanks, so far my category (from your code) is working flawless on my app!!Weyermann
See developer.apple.com/videos/play/wwdc2012/223 where Josh Shaffer "really discourages" us from manually calling touches... methods on next responder or other objects. There is also a solution in that video.Quadroon
R
2

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];
}
Ruthannruthanne answered 16/9, 2011 at 16:23 Comment(0)
P
0

"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

Paver answered 16/9, 2011 at 1:39 Comment(1)
Top as in hierarchically on top. As in all the other views are subviews of the scroll view. Well specifically there is one view that is a subview of the scroll view, and all the others are subviews of that, or subviews of subviews, etc.Ruthannruthanne
B
0

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;
        }
    }

}
Balancer answered 24/1, 2016 at 7:53 Comment(0)
T
-1

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.

Tyson answered 16/9, 2011 at 16:39 Comment(6)
It's not touchesBegan methods in the scroll view that aren't being called. They are on my view controller. On the other hand, touchesBegan on views that are "in" the scroll view are being called. It's quite the opposite problem. They can't get "out" of the scroll view.Ruthannruthanne
I still did'nt get your question completely, although I would like to point out that the touchesBegan method WILL work for a view within a scrollView!Tyson
I have looked at the documentation of UIScrollView and UIScrollViewDelegate and tried what's available. What I am trying to do varies from as simple as a tap gesture to a complex interpretation of touches as highlighting an area of the screen. I could switch it to gesture recognizers and that would probably fix the problem but a) I don't want to rewrite the code b) I want it to run in 3.0 which didn't have gesture recognizers.Ruthannruthanne
Hmm...well overriding nextResponder is definitely not recommended and you might encounter weird scrolling behaviour...but considering the pickle you're in, I can't think of anything elseTyson
UIScrollView subclasses will most certainly receive touchesBegan: and other events.Camillacamille
@Camillacamille True, but I don't think he ever talked about subclassing UIScrollView or even needs to.Tyson

© 2022 - 2024 — McMap. All rights reserved.