一、页面基本结构

  • 底层是一个垂直的scrollView,页面顶部是一个imageView。底部是一个tableView。如图:

二、思路一

  • 首先想到的是,既然是滚动视图,我们可以通过滚动视图的可滚动属性来实现吗?最开始,顶层具体业务的tableView禁止滚动。然后,根据事件响应链,滚动事件将由底层ScrollView接收和处理。达到最大偏移量后,底层ScrollView禁止滚动,顶层tableView同时打开,顶层可以滑动。不幸的是,现实是残酷的。
  • 因此,当偏移量达到临界值时,由于可滚动属性和最大偏移量的设置(将其中一个控件的可滚动属性 isScrollEnabled 设置为 false ),滚动手势将被截断,需要再次拖动才能继续滚动。显然,这种效果是不可接受的。

三、思路二

  • 使用定制手势。但是由于UIScrollView具有弹性效果,普通的滑动手势无法做到这一点,因此有必要引入UIDynamic仿真力场来实现阻尼效果。想了想,虽然在某种程度上是可行的,但是对于一个联动滑动来说,做这么多事情还是比较繁琐的,自定义手势的模拟弹性效果和原生ScrollView的效果可能还是有一定差距的,所以我选择放弃。

四、思路三

  • 回到第一个想法,除了边界位置会阻挡联动滚动,其他效果还是有可能的,那么能否通过手段解决这个问题呢?既然能写到这里,毫无疑问,绝对有可能。通过手势渗透,即滑动手势可以同时作用于底部的ScrollView和上部的tableView,并同时控制它们的滚动。通过底层滚动视图实现手势识别协议,同时响应滚动事件。
  • 1、先实现底层 scrollView 的协议方法 :shouldRecognizeSimultaneouslyWith方法,表示能同时识别两个gesture。
//MARK: - UIGestureRecognizerDelegate
extension UIScrollView: UIGestureRecognizerDelegate {
    //手势穿透,是否允许两个手势识别器同时识别手势(滑动手势可以同时作用于底部的ScrollView和上部的 tableView,并同时控制它们的滚动)
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}
  • 2、接着分别在底层容器和上层业务中实现滚动视图的代理方法 func scrollViewDidScroll(_ scrollView: UIScrollView),分别控制它们的可滚动状态和偏移量,从而达到目的。部分实现如下:
//MARK: - UIScrollViewDelegate
extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offsetY = scrollView.contentOffset.y
        print("正在滚动 - offsetY = \(offsetY), mainScrollEnabled = \(mainScrollEnabled), tableViewScrollEnabled = \(tableViewScrollEnabled), maxOffsetY = \(maxOffsetY)")
        if scrollView == mainScrollView {
            print("此时滚动的是mainScrollView - offsetY = \(offsetY), mainScrollEnabled = \(mainScrollEnabled), tableViewScrollEnabled = \(tableViewScrollEnabled), maxOffsetY = \(maxOffsetY)")
            if mainScrollEnabled {
                if scrollView.contentOffset.y >= maxOffsetY {
                    scrollView.contentOffset = CGPoint(x: 0, y: maxOffsetY)
                   // scrollView.setContentOffset(CGPoint(x: 0, y: maxOffsetY), animated: false)
                    mainScrollEnabled = false
                    tableViewScrollEnabled = true
                }
            } else {
                //如果mainScrollView 不能滚动,就设置它的偏移量为 临界值(图片的高度),使其不能滚动
                scrollView.contentOffset = CGPoint(x: 0, y: maxOffsetY)
               // scrollView.setContentOffset(CGPoint(x: 0, y: maxOffsetY), animated: false)
                //此时tableView是可以滚动的
                tableViewScrollEnabled = true
            }
        }
        if scrollView == tableView {
            print("此时滚动的是tableView - offsetY = \(offsetY), mainScrollEnabled = \(mainScrollEnabled), tableViewScrollEnabled = \(tableViewScrollEnabled), maxOffsetY = \(maxOffsetY)")
            if tableViewScrollEnabled {
                if scrollView.contentOffset.y <= 0 {
                    scrollView.contentOffset = CGPoint(x: 0, y: 0)
                    //使用setContentOffset:方法还是会有卡顿,
                   // scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
                    mainScrollEnabled = true
                    tableViewScrollEnabled = false
                }
            } else {
                scrollView.contentOffset = CGPoint(x: 0, y: 0)
               // scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
            }
        }
        print("End - offsetY = \(offsetY), mainScrollEnabled = \(mainScrollEnabled), tableViewScrollEnabled = \(tableViewScrollEnabled), maxOffsetY = \(maxOffsetY)")
    }
}