Rectangle 27 0

ios Continuous vertical scrolling between UICollectionView nested in UIScrollView?


class CollaborativeScrollView: UIScrollView, UIGestureRecognizerDelegate {

    var lastContentOffset: CGPoint = CGPoint(x: 0, y: 0)

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return otherGestureRecognizer.view is CollaborativeScrollView
    }
}
class YourViewController: UIViewController, UIScrollViewDelegate {

    private var mLockOuterScrollView = false

    @IBOutlet var mOuterScrollView: CollaborativeScrollView!
    @IBOutlet var mInnerScrollView: CollaborativeScrollView!

    enum Direction {
        case none, left, right, up, down
    }

    func viewDidLoad() {
        mOuterScrollView.delegate = self
        mInnerScrollView.delegate = self
        mInnerScrollView.bounces = false
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard scrollView is CollaborativeScrollView else {return}
        let csv = scrollView as! CollaborativeScrollView

        //determine direction of scrolling
        var directionTemp: Direction?
        if csv.lastContentOffset.y > csv.contentOffset.y {
            directionTemp = .up
        } else if csv.lastContentOffset.y < csv.contentOffset.y {
            directionTemp = .down
        }
        guard let direction = directionTemp else {return}

        //lock outer scrollview if necessary
        if csv === mInnerScrollView {
            let isAlreadyAllTheWayDown = (mInnerScrollView.contentOffset.y + mInnerScrollView.frame.size.height) == mInnerScrollView.contentSize.height
            let isAlreadyAlltheWayUp = mInnerScrollView.contentOffset.y == 0
            if (direction == .down && isAlreadyAllTheWayDown) || (direction == .up && isAlreadyAllTheWayUp) {
                mLockOuterScrollView = false
            } else {
                mLockOuterScrollView = true
            }
        } else if mLockOuterScrollView {
            mOuterScrollView.contentOffset = mOuterScrollView.lastContentOffset
        }

        csv.lastContentOffset = csv.contentOffset
    }
}

And that's it. This will stop your outer scrollview from scrolling when you begin scrolling the inner one, and get it to pick up again when the inner one is scrolled all the way to one of its ends.

Define a subclass of UIScrollView:

Since it's not possible to reroute touches to another view, the only way to ensure that the outer scrollview can continue scrolling once the inner one stops is if it had been receiving the touches the whole time. However, in order to prevent the outer one from moving while the inner one is, we have to lock it without setting its isScrollEnabled to false, otherwise it'll stop receiving the touches and won't be able to pick up where the inner one left off when we want to scroll past the inner one's top or bottom.

That's done by assigning a UIScrollViewDelegate to the scrollviews, and implementing scrollViewDidScroll(_:) as shown:

Note
Rectangle 27 0

ios Continuous vertical scrolling between UICollectionView nested in UIScrollView?


- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (scrollView == _scrollView || scrollView == _offersCollectionView) {

        CGFloat offersCollectionViewPosition = _offersCollectionView.contentOffset.y;
        CGFloat scrollViewBottomEdge = [scrollView contentOffset].y + CGRectGetHeight(scrollView.frame);

        if (scrollViewBottomEdge >= [_scrollView contentSize].height) {
            _scrollView.scrollEnabled = NO;
            _offersCollectionView.scrollEnabled = YES;
        } else if (offersCollectionViewPosition <= 0.0f && [_offersCollectionView isScrollEnabled]) {
            [_scrollView scrollRectToVisible:[_scrollView frame] animated:YES];
            _scrollView.scrollEnabled = YES;
            _offersCollectionView.scrollEnabled = NO;
        }
    }
}
_outerScrollView
_scrollView
_scrollView.pagingEnabled = YES;
  • If I swipe down (so the views up), the rest of the subviews come back into focus with the scrollView's bounce effect.
  • When I swipe up (so the view moves down), the offersCollectionView takes over the entire view, moving the other subViews out of the view.
  • _offersCollectionView is the _innerScrollView (which was UICollectionView #2 in my original post).
Note
Rectangle 27 0

ios Continuous vertical scrolling between UICollectionView nested in UIScrollView?


- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (scrollView == _scrollView || scrollView == _offersCollectionView) {

        CGFloat offersCollectionViewPosition = _offersCollectionView.contentOffset.y;
        CGFloat scrollViewBottomEdge = [scrollView contentOffset].y + CGRectGetHeight(scrollView.frame);

        if (scrollViewBottomEdge >= [_scrollView contentSize].height) {
            _scrollView.scrollEnabled = NO;
            _offersCollectionView.scrollEnabled = YES;
        } else if (offersCollectionViewPosition <= 0.0f && [_offersCollectionView isScrollEnabled]) {
            [_scrollView scrollRectToVisible:[_scrollView frame] animated:YES];
            _scrollView.scrollEnabled = YES;
            _offersCollectionView.scrollEnabled = NO;
        }
    }
}
_outerScrollView
_scrollView
_scrollView.pagingEnabled = YES;
  • If I swipe down (so the views up), the rest of the subviews come back into focus with the scrollView's bounce effect.
  • When I swipe up (so the view moves down), the offersCollectionView takes over the entire view, moving the other subViews out of the view.
  • _offersCollectionView is the _innerScrollView (which was UICollectionView #2 in my original post).
Note
Rectangle 27 0

ios Continuous vertical scrolling between UICollectionView nested in UIScrollView?


- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (scrollView == _scrollView || scrollView == _offersCollectionView) {

        CGFloat offersCollectionViewPosition = _offersCollectionView.contentOffset.y;
        CGFloat scrollViewBottomEdge = [scrollView contentOffset].y + CGRectGetHeight(scrollView.frame);

        if (scrollViewBottomEdge >= [_scrollView contentSize].height) {
            _scrollView.scrollEnabled = NO;
            _offersCollectionView.scrollEnabled = YES;
        } else if (offersCollectionViewPosition <= 0.0f && [_offersCollectionView isScrollEnabled]) {
            [_scrollView scrollRectToVisible:[_scrollView frame] animated:YES];
            _scrollView.scrollEnabled = YES;
            _offersCollectionView.scrollEnabled = NO;
        }
    }
}
_outerScrollView
_scrollView
_scrollView.pagingEnabled = YES;
  • If I swipe down (so the views up), the rest of the subviews come back into focus with the scrollView's bounce effect.
  • When I swipe up (so the view moves down), the offersCollectionView takes over the entire view, moving the other subViews out of the view.
  • _offersCollectionView is the _innerScrollView (which was UICollectionView #2 in my original post).
Note
Rectangle 27 0

ios Continuous vertical scrolling between UICollectionView nested in UIScrollView?


class CollaborativeScrollView: UIScrollView, UIGestureRecognizerDelegate {

    var lastContentOffset: CGPoint = CGPoint(x: 0, y: 0)

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return otherGestureRecognizer.view is CollaborativeScrollView
    }
}
class YourViewController: UIViewController, UIScrollViewDelegate {

    private var mLockOuterScrollView = false

    @IBOutlet var mOuterScrollView: CollaborativeScrollView!
    @IBOutlet var mInnerScrollView: CollaborativeScrollView!

    enum Direction {
        case none, left, right, up, down
    }

    func viewDidLoad() {
        mOuterScrollView.delegate = self
        mInnerScrollView.delegate = self
        mInnerScrollView.bounces = false
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard scrollView is CollaborativeScrollView else {return}
        let csv = scrollView as! CollaborativeScrollView

        //determine direction of scrolling
        var directionTemp: Direction?
        if csv.lastContentOffset.y > csv.contentOffset.y {
            directionTemp = .up
        } else if csv.lastContentOffset.y < csv.contentOffset.y {
            directionTemp = .down
        }
        guard let direction = directionTemp else {return}

        //lock outer scrollview if necessary
        if csv === mInnerScrollView {
            let isAlreadyAllTheWayDown = (mInnerScrollView.contentOffset.y + mInnerScrollView.frame.size.height) == mInnerScrollView.contentSize.height
            let isAlreadyAlltheWayUp = mInnerScrollView.contentOffset.y == 0
            if (direction == .down && isAlreadyAllTheWayDown) || (direction == .up && isAlreadyAllTheWayUp) {
                mLockOuterScrollView = false
            } else {
                mLockOuterScrollView = true
            }
        } else if mLockOuterScrollView {
            mOuterScrollView.contentOffset = mOuterScrollView.lastContentOffset
        }

        csv.lastContentOffset = csv.contentOffset
    }
}

And that's it. This will stop your outer scrollview from scrolling when you begin scrolling the inner one, and get it to pick up again when the inner one is scrolled all the way to one of its ends.

Define a subclass of UIScrollView:

Since it's not possible to reroute touches to another view, the only way to ensure that the outer scrollview can continue scrolling once the inner one stops is if it had been receiving the touches the whole time. However, in order to prevent the outer one from moving while the inner one is, we have to lock it without setting its isScrollEnabled to false, otherwise it'll stop receiving the touches and won't be able to pick up where the inner one left off when we want to scroll past the inner one's top or bottom.

That's done by assigning a UIScrollViewDelegate to the scrollviews, and implementing scrollViewDidScroll(_:) as shown:

Note