I have two nested UIScrollViews, both scrolling in the vertical direction. I need the outer scrollview to scroll to it's max range first before allowing the inner scrollview to scroll. The inner scrollview should not be scrollable until the outer scrollview has reached it's max range. Here's an illustration:
In the left diagram, a vertical drag inside of Scrollview B
should move Scrollview A
and Scrollview B
should not be scrollable (but it still needs to be able to receive touches/taps). Once Scrollview A
reaches it's max range (when Scrollview B
gets to the top of the screen), then Scrollview B
should scroll. This needs to work in one continuous motion.
I've attempted to toggle ScrollView B
's scrollEnabled
from ScrollView A
's scrollViewDidScroll:
delegate method, but this doesn't appear to be a viable solution because it doesn't work in one continuous motion (eg: The user needs to release and touch again after Scrollview B
reaches the top of the screen).
What's the best way to implement this such that is works in one continuous motion?
NestedScrollView is just like ScrollView, but it supports acting as both a nested scrolling parent and child on both new and old versions of Android. It is enabled by default. NestedScrollView is used when there is a need for a scrolling view inside another scrolling view.
A scrolling view inside of which can be nested other scrolling views, with their scroll positions being intrinsically linked.
I solved the problem in the following way. I am not really happy with it since it looks to me much too complicated, but it works (please note that the code below is a simplified, untested version of my code, which is due to a different UI more complicated):
I have 3 properties that control scrolling:
@property (nonatomic, assign) CGFloat currentPanY; @property (nonatomic, assign) BOOL scrollA; @property (nonatomic, assign) BOOL scrollB;
2-step scrolling:
Disable scrolling for B, and enable scrolling for A.
This allows to scroll A .
When A reaches its max position, disable scrolling for A, and enable scrolling for B:
-(void)scrollViewDidScroll: (UIScrollView *)scrollView { if (scrollView.contentOffset.y >= self.maxScrollUpOffset) { [scrollView setContentOffset:CGPointMake(0, self.maxScrollUpOffset) animated:NO]; self.scrollviewA.scrollEnabled = NO; self.scrollviewB.scrollEnabled = YES; self.scrollB = YES; } }
This gives the following effect:
When A is scrolled upwards it will stop scrolling when its max size is reached. However B will not start scrolling, since the pan gesture recognizer of A does not forward its actions to the pan gesture recognizer of B. Thus one has to lift the finger and to start a 2nd scrolling. Then, B will scroll. This gives the 2-step scrolling.
Continuous scrolling:
For continuous scrolling, B must scroll while the finger that started scrolling of A continues moving upwards. To detect this, I added a further pan gesture recognizer tho A, and enabled it to detect gestures simultaneously with the built-in gesture recognizers of A and B:
- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer *)otherGestureRecognizer { return YES; }
In the action of this additional pan gesture recognizer, I compute the distance the finger has moved upwards after the scrolling limit of A has been reached. By this distance, B is then scrolled programmatically:
- (void)panGestureRecognizerAction:(UIPanGestureRecognizer *)recognizer { if (recognizer.state != UIGestureRecognizerStateChanged) { self.currentPanY = 0; self.scrollB = NO; self.scrollA = NO; } else { CGPoint currentTranslation = [recognizer translationInView:self.scrollviewA]; CGFloat currentYup = currentTranslation.y; if (self.scrollA || self.scrollB) { if (self.currentPanY == 0) { self.currentPanY = currentYup; } CGFloat additionalYup = self.currentPanY - currentYup; if (self.scrollA) { CGFloat offset = self.scrollviewA.scrollUpOffset + additionalYup; if (offset >= 0) { self.scrollviewA.contentOffset = CGPointMake(0, offset); } else { self.scrollviewA.contentOffset = CGPointZero; } } else if (self.scrollB){ self.scrollviewB.contentOffset = CGPointMake(0, additionalYup); } } } }
There is still a disadvantage:
If you start scrolling, lift the finger, and let the scrollView decelerate, it will behave like the 2-stage scrolling, since the additional pan gesture recognizer won’t recognize any pan gesture.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With