Непрерывная вертикальная прокрутка между UICollectionView, вложенным в UIScrollView

Хотя я знаю, что вложенные scrollViews не идеальны, наши дизайнеры предоставили мне эту настройку, поэтому я прилагаю все усилия, чтобы заставить ее работать. Пусть начнется!

Просмотр иерархии

  • UIView
    • UIScrollView (только для вертикальной прокрутки)
      • UIImageView
      • UICollectionView # 1 (только для горизонтальной прокрутки)
      • UIImageView (отличается от предыдущего UIImageView)
      • UICollectionView # 2 (только для вертикальной прокрутки)

Важное примечание

Все мои представления определяются с помощью программной автоматической компоновки. Каждое последующее представление в иерархии подзаголовков UIScrollView имеет зависимость y-координаты от представления, которое было до него.

Проблема

Для простоты немного изменим номенклатуру:

  • _outerScrollView будет ссылаться на UIScrollView
  • _innerScrollView будет ссылаться на UICollectionView #2

Я бы хотел, чтобы мой _outerScrollView отправил свое событие касания к _innerScrollView, достигнув нижней части его contentSize. Я бы хотел, чтобы произошло обратное, когда я прокручиваю назад.

В настоящее время у меня есть следующий код:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    CGFloat bottomEdge = [scrollView contentOffset].y + CGRectGetHeight(scrollView.frame);
    if (bottomEdge >= [_outerScrollView contentSize].height) {
        _outerScrollView.scrollEnabled = NO;
        _innerScrollView.scrollEnabled = YES;
    } else {
        _outerScrollView.scrollEnabled = YES;
        _innerScrollView.scrollEnabled = NO;
    }
}

где начальные условия (до прокрутки) установлены на:

outerScrollView.scrollEnabled = YES;
innerScrollView.scrollEnabled = NO;

Что происходит?

При касании вида outerScrollView прокручивается до нижнего края, а затем имеет эффект резинкой из-за _outerScrollView.bounces = YES; Если я снова коснусь вида, прокрутка innerScrollView прокручивается до тех пор, пока он не достигнет своего нижнего края. На обратном пути тот же эффект резинкой имеет место в обратном порядке. То, что я хочу сделать, - это движение жидкости между двумя подзонами.

Очевидно, это связано с условиями scrollEnabled, которые установлены в условном выражении в фрагменте кода. Я пытаюсь выяснить, как направить скорость/скорость одного scrollView на следующий scrollView при ударе по краю.

Любая помощь в этом вопросе будет с благодарностью.

Другие заметки

  • Это не сработало для меня: https://github.com/ole/OLEContainerScrollView
  • Я рассматриваю возможность размещения всего в иерархии UIScrollView (кроме UICollectionView # 2) внутри UICollectionView # 2 supplementaryView. Не уверен, что это сработает.

Ответ 1

Выяснилось!

Сначала:

_scrollView.pagingEnabled = YES;

Во-вторых:

- (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;
        }
    }
}

Где:

  • _scrollView - это _outerScrollView
  • _offersCollectionView - это _innerScrollView (который был UICollectionView # 2 в моем исходном сообщении).

Вот что происходит сейчас:

  • Когда я прокручиваю (так что представление перемещается вниз), offersCollectionView берет на себя весь вид, перемещая другие подэкраны из представления.
  • Если я прокручу вниз (так что взгляды вверх), остальные части обзора возвращаются в фокус с эффектом отскока scrollView.

Ответ 2

Принятый ответ не работал для меня. Вот что сделал:

Определите подкласс 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
    }
}

Поскольку невозможно перенаправить касания в другое представление, единственный способ гарантировать, что внешнее представление прокрутки может продолжать прокрутку после остановки внутреннего, - это если он получал касания все время. Однако, чтобы предотвратить перемещение внешнего, в то время как внутренний, мы должны заблокировать его, не устанавливая его isScrollEnabled в false, в противном случае он перестанет получать прикосновения и не сможет определить, где находится внутренний. остановился, когда мы хотим прокрутить внутреннюю верхнюю или нижнюю часть.

Это делается путем назначения UIScrollViewDelegate для scrollviews и реализации scrollViewDidScroll(_:) как показано:

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
    }
}

И это оно. Это предотвратит прокрутку внешнего прокрутки, когда вы начнете прокручивать внутренний, и заставит его снова подхватиться, когда внутренний прокручивается до самого конца.