10
votes

I know there are similar posts on Stack but nothing that I've found has helped me find a solution.

  • I have a scrollview added to a UIView.
  • I have added a UIButton to the same UIView above the scrollview.

    1. I want the UIButton to respond to a touchUp and fire an event.
    2. If someone DRAGS on the UIButton I want it to pass the event to the scrollView and drag the UIScrollView without firing the button event.

I know how to pass on a touch with hitTest returning the scrollview but this stops anything getting to the UIButton.

I don't want to add the UIButton to the UIScrollView.

Does anyone have a proposed solution?

4
regarding this old question. If you have a subview, X, of a scroll view it's totally OK to make X have constraints that attached to the overall view and NOT to the scroll view! Very simply, X then will not scroll. It just sits on top. It's that easy!Fattie
Just to repeat that explanation: Say the button B is a subview of a scroll view S and the scroll view is sitting in an overall view, say V. Surprisingly, B does have to scroll. Again B does NOT have to scroll. Simply attach B to V (not to S) and you're done. It's actually that simple.Fattie
Thus looking at the question exactly as stated by Morgz, simply in point 2 YES make the button a subview of the scroll view, but surprisingly simply constraint the button to the overall UIView! It's that easy.Fattie

4 Answers

1
votes

As explained in this answer, a UIButton or other UIControl that is a subview of a scroll view will automatically respond to taps while letting the scroll view handle drags. However, buttons that are subviews of the scroll view will also move with the scrolled content, rather than remaining in the same position on the screen as the content scrolls beneath them. I assume this is why you don't want to put the buttons into the scroll view.

If that's the case, you can get the desired behavior with one more step. Put all your buttons into a UIView that is a subview of the scroll view, then in the scrollViewDidScroll method of the scroll view's delegate, set that view's frame.origin to the scroll view's contentOffset. I just implemented this in my app and it's working great, without having to do any subclassing or get involved with the responder chain or touch events.

1
votes

Try this:

  1. Add a transparent, custom UIView as big as the entire scroll view contentSize to the scroll view, over all the other views. (Or alternatively, make it as big as the scroll view width/height and simply move it every time the scroll view moves.) When the scroll view decides to pass on its touches to its subviews instead of dragging, this will be the first thing hitTested.
  2. Override the hitTest method for the UIView. Check if the touch is within your button frame. If it is, return the button; if not, return nil.

In effect, you'd be tricking the scroll view into thinking that the button is one of its subviews. I'm not certain this will work as I haven't actually tried it, but it seems entirely sensible.

1
votes

Try adding a UIPanGestureRecognizer to the UIBUtton. In the callback you specify to handle the pan (drag), you need not implement anything. Then, set cancelsTouchesInView to YES. This will cause your button to passed drag gestures to the other views.

        UIPanGestureRecognizer *gr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan)];
        [gr setCancelsTouchesInView:YES];
        [myButton addGestureRecognizer:gr];
        [gr release];

And here's handlePan:

-(void)handlePan
{
    // Do nothing
}

I ran into a similar situation where I had a UIScrollView with two UIView subviews. Each UIView contained a UISlider. I wanted to catch (and drop) vertical drags (which is where the gesture recognizer came in), but I also needed horizontal scrolls to work in the slider as you'd expect AND to pan the scroll view left and right. This was the solution. (The gesture recognizer was attached to each UIView.)

1
votes

Try this: Subclassing UIScrollView, return YES in the touchesShouldCancelInContentView: method.