一、页面基本结构
- 底层是一个垂直的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)")
}
}