阅读 649

解除嵌套 ScrollView / TableView 联动效果

这是我参与更文挑战的第4天,活动详情查看: 更文挑战

UIKit 中,当一个 scrollView(或其子类)的父控件中包含 scrollView 或其子类时,也就是说嵌套 ScrollView 时,当 scrollView 滑动到顶部或者底部时,再向上/向下滑动时,scrollView 类型的父控件会其联动。

直播间IM.png

这在大多数的情况下,保持了动画的连续性。但是有些情况需要禁止这种联动,比如直播类的 App,最外层为一个 TableView 在展示正在直播的直播间,在直播间中还会有一个消息展示的 tableView,此时就需要禁止滚动 IM 消息时禁止外层的 tableView 滚动。

如何取消这种联动呢?

方案一:子 ScrollView 开始滚动和停止时发送通知(失败)

我首先想到的是监听子 scrollView 的滚动事件,当子类开始滚动和结束滚动时发送通知,修改父 ScrollView 的 isScrollEnabled 属性,解除滚动。代码如下:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    // 发送开始滚动通知,让父类禁止滚动
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    let scrollToScrollStop = !scrollView.isTracking && !scrollView.isDragging && !scrollView.isDecelerating
    if (scrollToScrollStop) {
        didEndScroll()
    }
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if (!decelerate) {
        let scrollToScrollStop = !scrollView.isTracking && !scrollView.isDragging && !scrollView.isDecelerating;
        if (scrollToScrollStop) {
            didEndScroll()
        }
    }
}

func didEndScroll() {
    // 发送停止滚动通知,让父类开始滚动
}
复制代码

但是发现当在子 ScrollView 的可滚动的边界时,继续滚动,并不会触发 scrollViewDidScroll 方法,所以这种方案不可行。

方案二:重写子 ScrollView 的 hitTest 方法

因为父控件中的每次事件都会通过 hitTest 传递到子控件,所以可以通过响应者链,获取父 scrollView 控件,修改其 isScrollEnabled 属性,解除联动。

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    var responder = next
    // 父控件为 scrollView 的控件
    var superScrollView: UIScrollView?

    // 通过 next 响应者向上寻找父控件为 scrollView 的控件
    // 当 superScrollView != nil 时,说明按照条件获取到 scrollView 或其子类的父控件对象
    while responder != nil && superScrollView == nil {
        if let scrollView = responder as? UIScrollView {
            superScrollView = scrollView
        } else {
            responder = responder?.next
        }
    }

    let view = super.hitTest(point, with: event)

    // 如果 view != nil,说明手势操作是当前的 scrollView/tableView 发出的,
    // 则需要需要禁止父控件 superScrollView 的滚动;
    // 否则相反。
    if view != nil {
        superScrollView?.isScrollEnabled = false
    } else {

        superScrollView?.isScrollEnabled = true
    }

    return view
}
复制代码

通过实验发现这种方案可行。

其他

在修改 tableView 的 isScrollEnabled 时,发现会修改 tableView 的 contentOffset,造成 tableView 偏移量抖动。这个是因为自动设置 scrollView 的内边距造成的,可以通过修改下面的属性关闭:

// iOS 11 以前,修改 ViewController 的属性
automaticallyAdjustsScrollViewInsets = false
// iOS 11 之后,修改 ScrollView 的属性
tableView.contentInsetAdjustmentBehavior = .never
复制代码
文章分类
iOS
文章标签