user2393462435
user2393462435

Reputation: 2652

Handling touches for nested UIScrollViews scrolling in the same direction

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: Nested Scrollviews Diagram

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?

Upvotes: 35

Views: 12677

Answers (5)

Davide Candita
Davide Candita

Reputation: 406

In my case I solved subclassing UIScrollView for the outer ScrollView.

class MYOuterScrollView: UIScrollView, UIGestureRecognizerDelegate
{
    override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool
    {
        return true
    }

    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool
    {
         return true
    }
}

Upvotes: 5

Reinhard Männer
Reinhard Männer

Reputation: 15247

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.

Upvotes: 5

Sarat Patel
Sarat Patel

Reputation: 856

Same-Direction Scrolling

Same direction scrolling occurs when a UIScrollView that is a subview of a UIScrollView both scroll in the same direction. This is shown in the left image.

Follow this link if want to look in details https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/UIScrollView_pg/NestedScrollViews/NestedScrollViews.html

enter image description here

Just set some attributes which are mentioned in above image. it works.

Upvotes: -1

Clafou
Clafou

Reputation: 15400

Your requirement that this should work in one continuous motion calls out the answer: you need to use just one UIScrollView, not two.

If you have just one scrollview you can then perform your magic by overriding the scrollview's layoutSubviews method and rejigging its content to perform your parallax effect based on the current value of contentOffset. Make sure that contentSize always reflect the full height (you can even update contentSize inside layoutSubviews if needs be).

For an architecture, take your existing diagram and just replace Scrollview B with View B.

Upvotes: 3

Stephen Johnson
Stephen Johnson

Reputation: 5121

The gesture recognizer for scroll view A would need to pass off to the gesture recognizer for scroll view B to have on continuous motion which I am pretty sure is not possible. Why not combine the content of the two scroll views instead and then you would have one continuous motion. This code will combine the content of scrollView A and B into just A.

UIScrollView* scrollViewA = ...;
UIScrollView* scrollViewB = ...;
NSArray* subviews = scrollViewB.subviews;
for (int i = 0; i < subviews.count; i++)
{
    UIView* subview = [subviews objectAtIndex:i];
    CGRect frame = subview.frame;
    frame.origin.y += scrollViewA.contentSize.height;
    subview.frame = frame;
    [scrollViewA addSubview:subview];
}
CGSize size = scrollViewA.contentSize;
size.height += scrollViewB.contentSize.height;
scrollViewA.contentSize = size;

Upvotes: 1

Related Questions