mlin956 mlin956 - 3 months ago 12
iOS Question

Expand UIScrollView interactive area and differentiate swiping and tapping

I'm using UIScroll View to make a gallery-like ui with paging functionality. Basically like this:

enter image description here

Since I need paging, so I set the width of scrollview equals to the width of a single page, in my example, the width of the pink rectangular.

But I want two extra things:


  1. Tapping the yellow or blue area will bring the corresponding rectangular to the center.

  2. One can scroll/swipe on yellow or blue area (out of the scrollview), which means the entire width of the screen is scrollable.



I followed this thread and added
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
. BUT by doing so, I can only achieve my second goal. When I set selector or delegate handling tapping reaction of yellow and blue, it does't work. Any idea about it?

Answer

That answer you referenced is one of my old favorites. It doesn't contemplate your first requirement, but I think it can handle it very neatly with just the addition of a tap gesture recognizer.

Create it on your "ClipView":

UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
[self.myClipView addGestureRecognizer:tapGR];
// myClipView is the view that contains the paging scroll view

- (void)tap: (UITapGestureRecognizer *)gr {
    // there are a few challenges here:
    // 1) get the tap location in the correct coordinate system
    // 2) convert that to which "page" was tapped
    // 3) scroll to that page
}

Challenge 1) is easy thanks to the gesture recognizer, which answer locationInView:

CGPoint location = [gr locationInView:self.scrollView]; 

For challenge 2) we need to work out what page within your scroll view was tapped. That can be done with pretty simple arithmetic given the page width.

// assuming you have something like this
#define kPAGE_WIDTH    // some float

// page is just how many page-width's are represented by location.y
NSInteger page = floor(location.y/kPAGE_WIDTH);

Now, challenge 3) is easy now because we can change a page to it's scroll position straight-forwardly...

CGFloat y = page * kPAGE_WIDTH;
[self.scrollView setContentOffset:CGPointMake(y, 0.0f) animated:YES];

Or, all in one chunk of code...

- (void)tap: (UITapGestureRecognizer *)gr {
    CGPoint location = [gr locationInView:self.scrollView]; 
    NSInteger page = floor(location.y/kPAGE_WIDTH);
    CGFloat y = page * kPAGE_WIDTH;
    [self.scrollView setContentOffset:CGPointMake(y, 0.0f) animated:YES];
}

EDIT

You may also want to exclude the "current page" area from the gesture recognizer. That's simply done by qualifying the test in the tap method.

The only trick is to get the tap position in the same coordinate system as the scroll view's frame, that is, the clip view...

CGPoint locationInClipper = [gr locationInView:gr.view]; 

And the SDK provides a nice method to test...

BOOL inScrollView = [self.scrollView pointInside:locationInClipper withEvent:nil];

So...

- (void)tap: (UITapGestureRecognizer *)gr {
    CGPoint locationInClipper = [gr locationInView:gr.view]; 
    BOOL inScrollView = [self.scrollView pointInside:locationInClipper withEvent:nil];

    if (!inScrollView) {
        CGPoint location = [gr locationInView:self.scrollView]; 
        NSInteger page = floor(location.y/kPAGE_WIDTH);
        CGFloat y = page * kPAGE_WIDTH;
        [self.scrollView setContentOffset:CGPointMake(y, 0.0f) animated:YES];
    }
}