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

后端 未结 6 621
天涯浪人
天涯浪人 2021-02-09 18:12

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 a

相关标签:
6条回答
  • 2021-02-09 18:59

    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.

    0 讨论(0)
  • 2021-02-09 19:08

    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);
    }
    
    0 讨论(0)
  • 2021-02-09 19:09

    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.

    0 讨论(0)
  • 2021-02-09 19:09

    @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
    
    0 讨论(0)
  • 2021-02-09 19:14

    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).

    0 讨论(0)
  • 2021-02-09 19:16

    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.

    0 讨论(0)
提交回复
热议问题