【问题标题】:Continuous vertical scrolling between UICollectionView nested in UIScrollView嵌套在 UIScrollView 中的 UICollectionView 之间的连续垂直滚动
【发布时间】:2014-11-05 17:43:47
【问题描述】:

虽然我知道嵌套的滚动视图并不理想,但我们的设计师为我提供了这种设置,所以我正在尽我所能让它发挥作用。开始吧!

查看层次结构

  • UI 视图
    • UIScrollView(仅限垂直滚动)
      • UIImageView
      • UICollectionView #1(仅限水平滚动)
      • UIImageView(不同于之前的UIImageView)
      • UICollectionView #2(仅限垂直滚动)

重要提示

我所有的视图都是使用程序化自动布局定义的。 UIScrollView 的子视图层次结构中的每个连续视图都对其之前的视图具有 y 坐标依赖性。

问题

为了简单起见,让我们稍微修改一下命名法:

  • _outerScrollView 将引用 UIScrollView
  • _innerScrollView 将引用 UICollectionView #2

我希望我的_outerScrollView 在到达其 contentSize 的底部时将其触摸事件路由到_innerScrollView。当我向上滚动时,我希望发生相反的情况。

目前我有如下代码:

- (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 滚动直到它到达其底部边缘。在回来的路上,同样的橡皮筋效应以相反的顺序发生。我想要发生的是在两个子视图之间进行流畅的运动。

显然,这是由于在代码 sn-p 的条件中设置的scrollEnabled 条件。我想弄清楚的是如何在碰到边缘时将一个滚动视图的速度/速度路由到下一个滚动视图。

在此问题上的任何帮助将不胜感激。

其他说明

  1. 这对我不起作用:https://github.com/ole/OLEContainerScrollView
  2. 我正在考虑将 UIScrollView 层次结构中的所有内容(UICollectionView #2 除外)放入 UICollectionView #2 >supplementaryView。不确定这是否可行。

【问题讨论】:

  • 一位熟人在 Twitter 上向我推荐了以下视频:asciiwwdc.com/2013/sessions/217。我现在正在检查。
  • _innerScrollView 有多高?我的意思是它的frame,而不是它的contentSize。是和屏幕一样高吗?还是它比屏幕短,所以当它完全在屏幕上时,一些图像视图仍然可见?另外,_innerScrollView 使用什么布局?
  • 我最终弄明白了。为了回答你的问题,innerScrollView 的 frame 是设备的宽度,大约是 viewController 主视图高度的 1/3。

标签: ios objective-c uiscrollview uicollectionview ios8


【解决方案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 接管整个视图,将其他子视图移出视图。
  • 如果我向下滑动(因此视图向上),其余子视图将通过滚动视图的反弹效果重新成为焦点。

【讨论】:

    【解决方案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 分配给滚动视图并实现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
        }
    }
    

    就是这样。当您开始滚动内部滚动视图时,这将阻止您的外部滚动视图滚动,并在内部滚动视图一直滚动到其一端时再次拾取它。

    【讨论】:

    • 太棒了。一个问题,如果外部滚动视图使用内部滚动视图作为其缩放视图,这会起作用吗? (我们将设置它们有不同的委托,但“scrollViewDidScroll”仍然是相同的)。
    • 这个答案不适用于嵌套在滚动视图中的 UICollectionView 问题。