Horizontal UIScrollView having vertical UIScrollViews inside - how to prevent scrolling of inner scroll views when scrolling outer horizontal view?
Asked Answered
A

6

6

couldn't find a solution for that.

I am building an app, with big scroll view, who has paging (Horizontal). Inside this scroll view, there is a grid of UIView's, and an UIScrollview inside each one of them, with vertical scroll view.

Now, the point is, When I'm paging my 'big' scrollview, sometimes the touch get stuck in one of small scrollviews inside the UIViews of the grid.

I don't know how to avoid it - tried trick with hitTest but still couldn't find the answer.

Hope i'm clear...

Thanks for your help.

Edit:

This is the bigger scrollview:

@implementation UIGridScrollView
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    self.pagingEnabled;
    return self;
}
@end

Now, to this UIGridScroll View, I added as a subview this view:

@implementation UINoteView
{
IBOutlet UIScrollView *_innerScrollView; // this scrollview frame is in the size of the all UINoteView
}

- (void)awakeFromNib
{
    _innerScrollView.contentSize = CGSizeMake(_innerScrollView.frame.size.width, _innerScrollView.frame.size.height+50.0f);
}
@end

The paging works well, the inner scroll view works well, but too many times when I paging the bigger note view, my finger 'get stuck' in the _innerScrollView.

Thanks!

Airliah answered 7/11, 2012 at 15:31 Comment(4)
Could you please post some code showing how you are setting up your scrollviews? If there isn't anywhere that a user could touch and be touching the left-right (parent) scrollview, the user's touches will always register within a smaller up-down scrollview.Meridional
Thanks, tried to add something..Airliah
I have a similar application. I think there is a margin with which the system decides whether to scroll the inner scrollview or the external one. However, I don't think it'll solve your issue but you can try to avoid the use of a middle UIView (UINoteView) and add your innerScrollView directly inside the outer one. This is what I did in my case and the user experience is not bad at all.Ironworker
I want to join @AviTsadok question by opening a bounty on this question.Coterminous
G
3

@stanislaw, I've just tried the solution you suggest on an iPhone device.

I see your problem.

Your code does prevent occasional scrolls of vertical views but I believe it is not the simultaneous gesture recognition does the job - comment the entire code you provide for inner views and use the code for the outer scroll view with the following modification:

@interface OuterHorizontalScrollView : UIScrollView ...
@property (weak) InnerVerticalScrollView *currentActiveView; // Current inner vertical scroll view displayed.
@end

@implementation OuterHorizontalScrollView
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (self.currentActiveView.dragging == NO) {
        self.currentActiveView.scrollEnabled = NO; // The presence of this line does the job
    }
    return YES;
}

- (void)scrollViewDidEndDragging:(PlacesScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    self.currentActiveView.scrollEnabled = YES; // This is very likely should be done for all subviews, not only a current.
}    
@end
Gusto answered 24/4, 2013 at 5:47 Comment(2)
it really seems to work without any additional code for inner scroll views, but I need to check it more. thanks for the answer!Coterminous
@AviTsadok, I wonder, if this solution derived from my solution and simplified even more does solve your original question as well as it does solve mine.Coterminous
F
0

You could subclass UIScrollView and implement gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:, allowing the scroll view's built-in panGestureRecognizer to recognize simultaneously with another scroll view's gesture recognizers.

Example:

//This is needed because the public UIScrollView headers
//don't declare the UIGestureRecognizerDelegate protocol:
@interface UIScrollView (GestureRecognition) <UIGestureRecognizerDelegate>
@end

@interface MyScrollView : UIScrollView

@end

@implementation MyScrollView

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    if (gestureRecognizer == self.panGestureRecognizer) {
        return YES;
    } else if ([UIScrollView instancesRespondToSelector:@selector(gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:)]) {
        //Note: UIScrollView currently doesn't implement this method, but this may change...
        return [super gestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer];
    }
    return NO; //the default
}

@end

Using this subclass for either the horizontal, or all vertical scroll views should suffice.

You might actually find that you like the default behavior better, after you've tried using it this way. Allowing both views to scroll simultaneously will almost always lead to accidental vertical scrolling while swiping left and right, which can be irritating (most people don't do a perfectly horizontal swipe gesture).

Favoritism answered 17/4, 2013 at 17:32 Comment(4)
Thanks for the answer, I didn't know about shouldRecognizeSimultaneously... I will try it now.Coterminous
So... are you suggesting me simultaneous scrolling instead of preventing one of them (vertical) from scrolling at all, like the way the question asks, right?Coterminous
It looks cool (yeah!), but it is not what this question asks about. Though I am thinking about how this trick maybe be used for prevention.Coterminous
It shouldn't be too hard to disable scrolling in the vertical views while the horizontal view is scrolling, but then you'll have the opposite problem (namely, getting "stuck" in the horizontal scroll direction).Favoritism
C
0

Here is how I have done it:

Using shouldRecognizeSimultaneouslyWithGestureRecognizer (thanks @omz!) and custom swipe gesture recognizer in vertical scroll views (see the similar question

Horizontal scrolling UIScrollView with vertical pan gesture), I have the following setup:

@interface UIScrollView (GestureRecognition) <UIGestureRecognizerDelegate>
@end

@interface OuterHorizontalScrollView : UIScrollView ...
@property (weak) InnerVerticalScrollView *currentView; // Current inner vertical scroll view displayed.
@end

@implementation OuterHorizontalScrollView
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (self.currentActiveView.placeVerticalScrollView.dragging == NO) {
        self.currentActiveView.placeVerticalScrollView.scrollEnabled = NO;
        return YES;
    } else {
        return NO;
    }
}    
@end

@interface InnerVerticalScrollView : UIScrollView...
@property UISwipeGestureRecognizer *swipeGestureRecognizer;
@end

@implementation InnerVerticalScrollView
- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        ...
        self.swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeGesture:)];
        self.swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionDown | UISwipeGestureRecognizerDirectionUp;
        [self addGestureRecognizer:self.swipeGestureRecognizer];
    }

    return self;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (gestureRecognizer == self.panGestureRecognizer && self.scrollEnabled == YES) {
        return YES;
    } else if (gestureRecognizer == self.swipeGestureRecognizer) {
        return YES;
    } else {
        self.scrollEnabled = NO;
    }

    return NO;
}

- (void)handleSwipeGesture:(UIGestureRecognizer *)sender {
    self.scrollEnabled = YES;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {    
    return YES;
}

This code is a bit hacky: it allows scrolling of vertical scroll views only if their custom Swipe gesture is recognized (only top or bottom directions), all other gestures are passed to outer scroll view which in its turn allows any gestures only if no one of inner scroll views are being dragged.

Note: it was not obvious that swipe gesture works for slow or tiny swipes too, but it does (see also comments to quoted question above).

I feel like it could be accomplished easier and maybe I will refactor it later.

Anyway, now outer scroll works perfectly - it scrolls horizontally without any occasional vertical pans of inner scroll views.

LATER UPDATE: As I expected before, my solution did contain unnecessary bits of code: see the answer by @burik, while being partially based on my solution, it takes these bits out of it.

Coterminous answered 17/4, 2013 at 23:10 Comment(1)
@ Avi Tsadok, I am interested, if my solution works for you too. Or, please post yours if you've come up with your own.Coterminous
A
0

I'm a little big confused on your setup but you might want to look into -[UIScrollView setDelaysContentTouches:]. You can use it to add a bit of precedence when dealing with nested scroll views.

Amick answered 23/4, 2013 at 2:50 Comment(6)
The simplicity of your solution made me thinking that I'am completely wrong in what I've done and I wanted to try your solution as quickly as possible. But... the tests on my device showed me that it does not prevent occasional vertical drags of inner scroll views. So, my solution still does much better job from the terms of usability, though it looks like a hack.Coterminous
The other option is I didn't fully understand your setup. For reference here is the demo app I put together to look into this. cl.ly/0Y400J3w183aAmick
Yes, you are catching it the right way. I believe I don't need to adapt my provisioning profiles to test your example on my phone - I believe that just trying setDelaysContentTouches: on my setup is pretty enough, since they are the same!Coterminous
I can suggest you to look at what I've done in my answer from the following point of view: I want horizontal scrolling to be fast and smooth and I want it to take a higher, say 1.5x, priority over the vertical scrolls of inner views, and so I add additional swipe gesture recognizer to narrow the scope of available gestures accepted by my inner views: (see next comment)Coterminous
...continuing previous comment: I need only the gestures which are FOR SURE TOP OR BOTTOM SWIPES, and I DON'T NEED any other gestures recognized by default .panGestureRecognizer of my vertical inner scroll views. All this stuff is done to provide smooth horizontal scrolling not interrupted by occasional vertical tiny scrolls breaking the smoothness of horizontal scrollling of outer view, that I consider more important in the scope of this question/setup!Coterminous
I hope, I am clear in my explanation. And thanks for your help! I will repeat it again - I could reach the desired effect only by doing the setup I've described in my answer. All other setups led me to a common behavior of scroll views: vertical pans do occasionally break the smoothness of horizontal scrolling of outer view.Coterminous
A
0

Once i have faced this situation & i have the following work around to done this.

Subclass both of your view from UIScrollView & you will be fine with this. In your case i can see that your UINoteView is not subclass from UIScrollView, subclass it from ScrollView & remove the innerScrollView from this class.

Appeasement answered 23/4, 2013 at 12:22 Comment(2)
the fact that I opened a bounty on the question I don't own, didn't allow me to edit the original question, so it could meet the conditions I actually have. This made a confusion: in contrast to conditions of this question, I DO have a setup you describe (both kinds of views are subclasses of UIScrollView) and I STILL have a problem described.Coterminous
If it helps any this is the quick sample I put together to play with, and I didn't really notice much problems with scrolling. However, I may have misunderstood your setup.Amick
H
0

I think using velocity to determine the scroll direction is a better way:

- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
    CGPoint velocity = [gestureRecognizer velocityInView:gestureRecognizer.view];
    return fabs(velocity.y) > 3 * fabs(velocity.x);
}
Hobby answered 25/10, 2013 at 13:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.