7

我有以下布局(见下文),在大多数情况下都可以正常工作。用户可以在蓝色范围内滚动UIScrollView(出于所有意图和目的,它是 a UITableView,但这个问题概括了这一点),然后当他们到达此滚动视图的末尾时,他们可以再次开始滚动(他们必须采取他们的手指关闭,然后再次打开,因为内部滚动视图橡皮筋,否则),“超级”滚动视图开始滚动,显示图像的其余部分。

这是我不想要的全部(他们必须松开手指,然后再次打开,因为内部滚动视图会橡皮筋) 。理想情况下,一旦包含UIScrollView的内容到达其内容的末尾,我希望超级视图立即接管滚动,以便内部滚动视图不会橡皮筋。

当用户向上滚动时也是如此;当红色滚动视图到达其内容的顶部时,我希望内部蓝色滚动视图立即开始向上滚动,而不是顶部的红色滚动视图橡皮筋。

屏幕布局; UIScrollView + UIImageView 在 UIScrollView

知道怎么做吗?我能够确定滚动视图何时到达其内容的末尾,但我不确定如何应用这些知识来达到我想要的效果。谢谢。

// Inner (blue) scroll view bounds checking
if (scrollView.contentOffset.y + scrollView.frame.size.height > scrollView.contentSize.height) { ... }

// Outer (red) scroll view bounds checking
if (scrollView.contentOffset.y < 0) { ... }
4

2 回答 2

19

是的。我为你准备了一个绝妙的技巧。

与其让你的红色轮廓视图成为一个滚动视图,不如让它成为一个填充屏幕的普通 UIView。在该视图中布置您的滚动视图(表格视图)和图像视图,就像它们在您的插图中一样。

在所有其他滚动视图和图像视图上方放置一个填充根视图边界(即也填充屏幕)的滚动视图。将此视图的内容大小设置为要滚动的所有视图的总内容高度。换句话说,在所有其他视图之上有一个不可见的滚动视图,其内容大小高度是内部滚动视图(表格视图)内容大小高度 + 图像视图大小高度。

层次结构应如下所示:

在此处输入图像描述

然后,您使用非常高的内容大小制作的顶部滚动视图使其代表成为您的视图控制器。实施scrollViewDidScroll,我们将发挥一些作用。

Scrollviews 基本上是通过使用时髦的动量公式调整边界原点来滚动的。所以在我们的scrollviewDidScroll方法中,我们将简单地调整底层视图的边界:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{

    //the scroll view underneath (in the container view) will have a max content offset equal to the content height
    //but minus the bounds height
    CGFloat maxYOffsetForUnderScrollView = self.underScrollView.contentSize.height - self.underScrollView.bounds.size.height;

    CGRect scrolledBoundsForContainerView = self.view.bounds;

    if (scrollView.contentOffset.y <= maxYOffsetForUnderScrollView) {
        //in this scenario we are still within the content for the underScrollView
        //so we make sure the container view is scrolled to the top and set the offset for the contained scrollview
        self.containerView.bounds = scrolledBoundsForContainerView;
        self.underScrollview.contentOffset = scrollView.contentOffset;
        return;
    }

    //in this scenario we have scrolled throug the entirety of the contained scrollview
    //set its offset to the max and change the bounds of the container view to scroll everything else.
    self.underScrollView.contentOffset = CGPointMake(0, maxYOffsetForUnderScrollView);

    scrolledBoundsForContainerView.origin.y = scrollView.contentOffset.y - maxYOffsetForUnderScrollView;
    self.containerView.bounds = scrolledBoundsForContainerView;
}

你会发现,当每一帧动画都调用 scrollViewDidScroll 时,包含视图的这种虚假滚动看起来非常自然。

可是等等!我听你说。顶部的滚动视图现在拦截所有触摸,并且它下面的视图也需要被触摸。我对此也有一个有趣的解决方案。

将顶部的滚动视图设置为在屏幕外某处(即,将其框架设置为屏幕外,但大小仍然相同。)然后在您的viewDidLoad方法中将滚动视图添加panGestureRecogniser到主视图中。这意味着您无需在屏幕上实际看到视图即可获得所有 iOS 自然滚动动力和内容。包含的滚动视图现在可能会变得不稳定,因为它的平移手势识别器也会被调用(它们的工作方式与 UIEvent 处理不同),因此您需要将其删除。

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    [self.view addGestureRecognizer:self.scrollview.panGestureRecognizer];

    [self.underScrollview removeGestureRecognizer:self.underScrollView.panGestureRecognizer];

    //further code to set up content sizes and stuff
}

我玩得很开心,所以这里有一个指向 github 上示例项目的链接: https ://github.com/joelparsons/multipleScrollers

编辑:

要在顶部滚动视图离开屏幕时显示滚动条,无论您将它放在哪里,您都可以将其设置为scrollIndicatorInsets这样创建的插图:

CGPoint scrollviewOrigin = self.scrollview.frame.origin;
self.scrollview.scrollIndicatorInsets = UIEdgeInsetsMake(-scrollviewOrigin.y,0,scrollviewOrigin.y,scrollviewOrigin.x);

*警告滚动视图仍然必须是正确的高度,但我相信你明白了。

然后要使栏在滚动视图的可见边界之外绘制,您必须关闭剪辑到边界

self.scrollview.clipsToBounds = NO;
于 2012-11-04T19:34:30.680 回答
0

我的天啊。jackslash,你救了我的命。

就我而言,我需要使用三个深度的滚动视图。

  1. 父滚动视图
    • 作为孩子之一具有滚动视图
  2. 子滚动视图:在下图中显示为“说明滚动视图/查看表视图/信息滚动视图”)
  3. 隐藏的滚动视图
    • 将自己的内容偏移量分配给“父滚动视图”和“子滚动视图”
    • 它具有整个展平内容的内容大小

视图层次结构的屏幕截图

设置视图层次结构后,我只需要同步整个展平内容大小并正确分配内容偏移量。

    Observable.combineLatest(
        overviewStackView.rx.observe(CGRect.self, #keyPath(UIStackView.frame)).unwrap(),
        explanationScrollView.rx.observe(CGSize.self, #keyPath(UIScrollView.contentSize)).unwrap()
      )
      .subscribe(onNext: { [weak self] overviewStackViewFrame, explanationScrollViewContentSize in
        guard let self = self else { return }
        let totalContentHeight = overviewStackViewFrame.height + self.segmentedControl.frame.height + explanationScrollViewContentSize.height
        self.hiddenScrollView.contentSize.height = totalContentHeight
      })
      .disposed(by: disposeBag)

    hiddenScrollView.rx.didScroll
      .subscribe(onNext: { [weak self] _ in
        guard let self = self else { return }
        let currentHiddenScrollViewOffsetY = self.hiddenScrollView.contentOffset.y
        let parentScrollViewMaxOffsetY = self.overviewStackView.frame.height
        let expectedChildScrollViewOffsetY = max(currentHiddenScrollViewOffsetY - parentScrollViewMaxOffsetY, 0)
        self.parentScrollView.contentOffset.y = min(parentScrollViewMaxOffsetY, currentHiddenScrollViewOffsetY)
        self.explanationScrollView.contentOffset.y = expectedChildScrollViewOffsetY
      })
      .disposed(by: disposeBag)
于 2020-08-19T10:16:18.313 回答