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!

Stanislav Pankevich
  • 11,044
  • 8
  • 69
  • 129
Avi Tsadok
  • 1,843
  • 13
  • 19
  • 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. – propstm Nov 07 '12 at 19:46
  • Thanks, tried to add something.. – Avi Tsadok Nov 08 '12 at 07:11
  • 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. – micamoita Nov 08 '12 at 09:05
  • I want to join @AviTsadok question by opening a bounty on this question. – Stanislav Pankevich Apr 17 '13 at 16:54

6 Answers6

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
burik
  • 141
  • 4
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).

omz
  • 53,243
  • 5
  • 129
  • 141
  • Thanks for the answer, I didn't know about shouldRecognizeSimultaneously... I will try it now. – Stanislav Pankevich Apr 17 '13 at 17:43
  • 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? – Stanislav Pankevich Apr 17 '13 at 18:03
  • 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_. – Stanislav Pankevich Apr 17 '13 at 18:10
  • 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). – omz Apr 17 '13 at 18:41
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.

Community
  • 1
  • 1
Stanislav Pankevich
  • 11,044
  • 8
  • 69
  • 129
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.

axiixc
  • 1,962
  • 18
  • 25
  • 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. – Stanislav Pankevich Apr 23 '13 at 14:50
  • 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. http://cl.ly/0Y400J3w183a – axiixc Apr 24 '13 at 04:19
  • 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! – Stanislav Pankevich Apr 24 '13 at 04:42
  • 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) – Stanislav Pankevich Apr 24 '13 at 04:43
  • ...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! – Stanislav Pankevich Apr 24 '13 at 04:44
  • 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. – Stanislav Pankevich Apr 24 '13 at 04:49
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.

Amjad Khan
  • 393
  • 3
  • 13
  • 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. – Stanislav Pankevich Apr 23 '13 at 14:53
  • If it helps any [this](http://cl.ly/0Y400J3w183a) 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. – axiixc Apr 23 '13 at 19:48
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);
}
an0
  • 17,191
  • 12
  • 86
  • 136