4

I have a UIView inside a UIScrollView, inside the UIView is a button. The issue is that when I press that button, hold it, (in this case the button state is pressed) and try to scroll, my scroll view doesn't scroll. In which it should. The UIView has a gesture recognizer in it and I am trying to use one of it's delegate to allow the scroll view to be scrolled if I have my hands pressed the UIButton and scroll. How do I do this?

Basically to summarize, I need to pass the touch event to the scroll view if the button is pressed/hold. If it's a touch up event from the button then clearly it should trigger the action of the button and not scroll.

xonegirlz
  • 8,889
  • 19
  • 69
  • 127
  • 1
    It might be easier to add a selector in your button for the UIControlEvent: UIControlEventTouchUpInside – eric.mitchell Jul 16 '12 at 15:30
  • @Rickay I added more context to the question above, might help to clarify – xonegirlz Jul 16 '12 at 15:34
  • Near as I can tell, nothing you're trying to do here requires a gesture recognizer. It can all be accomplished with a normal scroll view and normal `UIButton`. – Jonathan Grynspan Jul 16 '12 at 15:37
  • @JonathanGrynspan then how do you do that? – xonegirlz Jul 16 '12 at 15:44
  • By putting the button in the scroll view. Touches to a button will be cancelled if held. – Jonathan Grynspan Jul 16 '12 at 16:18
  • @JonathanGrynspan er, I'm not sure about that. If you do a long press over a button in a scroll view, the opposite happens, that the button will not be canceled, but rather it take over. It will detect touch up inside successfully, but, regardless, if you move during the long press, it will _not_ be passed to the scroll view. – Rob Jul 16 '12 at 16:21
  • @JonathanGrynspan yea, that's actually not the case – xonegirlz Jul 16 '12 at 16:26
  • @RobertRyan any ideas on how to solve this? – xonegirlz Jul 16 '12 at 16:26
  • I'm wondering if you can set up your own custom gesture recognizer for the button for which, upon panning you will manually scroll the scroll view, and if you haven't panned by the time you get to `touchesEnded` (and if you're still over the button), then you do your button click action. Maybe, not sure if you can get that to work. – Rob Jul 16 '12 at 16:30
  • @xonegirlz Yes, yes it is the case. Your behaviour should match the standard iOS scrollview behaviour. Attempting to manipulate touch events is just going to piss off your users who expect a different response. Don't fight the SDK. – Jonathan Grynspan Jul 16 '12 at 16:40
  • @JonathanGrynspan but it is annoying that when I try to scroll and then hit the UIButton and the state is pressed, then the scroll view doesn't scroll.. this is not a good UX – xonegirlz Jul 16 '12 at 16:46
  • I agree with Jonathan's latter point that it is generally not a good idea to change established behavior unless you have a very compelling reason. I have an app that uses some non-standard behavior for no good reason (I'm not saying this is true in your case, though ... I don't know why you're trying to do this) and it is annoying. – Rob Jul 16 '12 at 16:47
  • I could make an argument for the standard behavior. If you have shaky hands (like my dad), it's nice to not flick the scroll view if that wasn't your intent. – Rob Jul 16 '12 at 16:50
  • @RobertRyan: I have non-shaky hands and that happens to me from time to time. But the behaviour of scroll views is consistent between other apps, and subtle changes to that behaviour will be like a mindscrew to your muscle memory. – Jonathan Grynspan Jul 16 '12 at 16:57
  • I have the same situation, and it works fine for me, also after holding the button. Are you sure you don't have some other gesture recognizer that prevents the UIScrollView from detecting the drag? Also, make sure you have done what is stated in http://stackoverflow.com/a/3550157/908621 – fishinear Mar 22 '13 at 18:04

4 Answers4

5

Old question, but I just had this issue and thought people would benefit from the answer. If you have a UIControl inside a UIScrollView, by default the scroll won't cancel the touch event. The solution is to subclass the UIScrollView like this:

@implementation PaginationScrollView {}

- (id)init {
    self = [super init];
    if (self) {
        self.canCancelContentTouches = YES;
    }
    return self;
}

- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
    return YES;
}

@end

the default implementation of touchesShouldCancelInContentView returns NO if the view is a UIControl.

Eduardo Scoz
  • 24,653
  • 6
  • 47
  • 62
  • initWithFrame: is the designated initializer of UIScrollview, so you should put initialization into that one. 'init' is not guaranteed to be called. – fishinear Mar 22 '13 at 18:07
3

Make sure setting

yourScrollView.canCancelContentTouches = YES;

Still not working? Because it only cancels touches instead of UIControlEvents such as UIControlEventTouchUpInside

How to solve? Add this to the top of your .m file

@implementation UIScrollView (TouchesShouldCancelInContentView)

- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
    return YES;
}

@end
Tim Chen
  • 1,364
  • 12
  • 14
  • 1
    This is called category bashing and not considered good code style. Do not override methods in categories. Better create a subclass. – fabb Nov 26 '15 at 15:41
  • @fabb I wouldn't say it is not a good code style. It depends on developer's design. For example I need all the scroll views in my app to cancel the touch, so categorizing and overriding should be a good choice in my case. However if a developer wants to cancel the touch in certain scroll views, subclassing is better. Thanks for pointing it out tho! Good point! – Tim Chen Nov 26 '15 at 18:04
0

You don't need to add a UIGestureRecognizer for a simple TouchUpInside action for a UIButton, simply do something like this:

[button addTarget:self action:@selector(buttonSelect:) forControlEvents:UIControlEventTouchUpInside];

then create the selector:

-(IBAction)buttonSelect:(id)sender{//do stuff here}
jacerate
  • 456
  • 3
  • 8
  • I added a context to the question above.. I hope it clarifies stuff more – xonegirlz Jul 16 '12 at 15:34
  • Does it scroll if you do it quickly? Like just a swipe gesture which originates in the button? I think if you press and hold down the device won't register anymore touches until it deals with the current one EDIT: I tested with one of my projects with programmatically added buttons in a scrollView, it scrolls if you go quickly, but halts if you press and hold on the button. The only thing I can think of is making the buttons uninteractable, then when the touch ends, grab the coordinates and call the selector while passing the tag of the button, which you find out in code based on bounds – jacerate Jul 16 '12 at 15:37
  • let me know if you needs some more explanation or an example – jacerate Jul 16 '12 at 15:40
  • if I just scroll on top of the button fast then yes scrolling is not an issue.. the issue is when I press and hold the button and then try to scroll.. now that won't scroll – xonegirlz Jul 16 '12 at 15:43
  • the drawback of your solution would be the button doesn't have a pressed state – xonegirlz Jul 16 '12 at 16:14
0

You could try making the UIButton non-interactable:

button.userInteractionEnabled = NO;

then add a UITapGestureRecognizer to the button:

UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(buttonPressed)];
[button addGestureRecognizer:recognizer];

That way the button will only react to touch events if it is tapped, all other events will go to the scroll view.

Setting userInteractionEnabled = NO may prevent the UITapGestureRecognizer from ever firing its event, in which case you could make the button a UIView or a UIImageView.

Casey
  • 2,393
  • 1
  • 20
  • 22