iOS 吸顶,双 ScrollView 处理

场景

双ScrollView

向上滚动时,先 rootScrollView 向上滚动,等到 header 消失,containerScrollView 到达顶部时,containerScrollView 再开始滚动。向下滚动时,等到 containerScrollView 滚动到顶部不能继续滚动时,rootScrollView 才可以开始滚动,显示 header。

方法

  • 根据 scrollView 的都偏移量,设置 rootScrollView 和 containerScrollView 的 isScrollEnabled。缺点:scrollView 的 isScrollEnabled 切换时,平移手势会中断,滚动不连贯。

  • rootScrollView 和 containerScrollView 可以同时识别平移手势,限制其 contentOffset 。(下面会详细描述)

    1. 设置 rootScrollView 和 containerScrollView 可以同时滚动
    2. containerScrollView.bounces = false,否则也会出现滑动不连贯
    3. rootScrollView 可以滚动时,containerScrollView 不能滚动,保持 contentOffset 不变
    4. containerScrollView 可以滚动时,rootScrollView 不能滚动,保持 contentOffset 不变

代码实例

设置 rootScrollView 和 containerScrollView 可以同时滚动

只要其中一个 scrollView 实现了 UIGestureRecognizerDelegate,并且 gestureRecognizer(_:shouldRecognizeSimultaneouslyWith otherGestureRecognizer:) 允许和另一个 scrollView 同时识别平移手势。这里简单起见,直接返回 true

class MyScrollView: UIScrollView, UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

rootScrollView 可以滚动时,containerScrollView 不能滚动,保持 contentOffset 不变

保持 containerScrollView 的 contentOffset.y 不变,在 containerScrollView 的 delegate 的 scrollViewDidScroll(_:) 中设置 contentOffset.y = 0。在设置之前,要判断 containerScrollView 能否滚动。不能根据 containerScrollView.contentOffset.y 判断,因为进入 scrollViewDidScroll(_:) 时,containerScrollView.contentOffset.y 已经发生改变。所以判断条件为,rootScrollView.contentOffset.y 等于 containerScrollView 在 rootScrollView 中的偏移量时,containerScrollView 可以滚动。

    var containerScrollViewOffsetY: CGFloat {
        return containerScrollView.convert(containerScrollView.bounds, to: rootScrollView).minY
    }

    func canContainerScrollViewScroll() -> Bool {
        return rootScrollView.contentOffset.y >= containerScrollViewOffsetY
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView === containerScrollView {
            guard !canContainerScrollViewScroll() else {
                return
            }
            containerScrollView.contentOffset.y = 0
        }
    }

containerScrollView 可以滚动时,rootScrollView 不能滚动,保持 contentOffset 不变

同理,在 rootScrollView 的 scrollViewDidScroll(_:) 中,不能根据 rootScrollView.contentOffset.y 判断 rootScrollView 是否可以滚动,而是通过
containerScrollView.contentOffset.y == 0 判断。

    func canRootScrollViewScroll() -> Bool {
        return containerScrollView.contentOffset.y == 0
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView === rootScrollView {
            guard !canRootScrollViewScroll() else {
                return
            }
            scrollView.contentOffset.y = containerScrollViewOffsetY
        }
    }

完整代码


import UIKit
import SnapKit

class ViewController: UIViewController {
    
    let rootScrollView = MyScrollView()
    let headerView = UIView()
    let containerScrollView = UIScrollView()
    let label = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        setupUI()
    }

    func setupUI() {
        let rootView = UIView()
        view.addSubview(rootView)
        rootView.snp.makeConstraints { make in
            make.edges.equalTo(view.safeAreaLayoutGuide)
        }
        
        rootView.addSubview(rootScrollView)
        rootScrollView.addSubview(headerView)
        rootScrollView.addSubview(containerScrollView)
        containerScrollView.addSubview(label)
        
        rootScrollView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }
        rootScrollView.delegate = self
//        rootScrollView.bounces = false
        
        headerView.backgroundColor = .blue
        headerView.snp.makeConstraints { make in
            make.left.right.top.equalToSuperview()
            make.width.equalTo(rootView)
            make.height.equalTo(100)
        }
        
        containerScrollView.snp.makeConstraints { make in
            make.left.right.bottom.equalToSuperview()
            make.top.equalTo(headerView.snp.bottom)
            make.height.equalTo(rootView)
        }
        containerScrollView.delegate = self
        // 2. containerScrollView.bounces = false
        containerScrollView.bounces = false
        
        label.snp.makeConstraints { make in
            make.width.equalTo(rootView)
            make.edges.equalToSuperview()
        }
        label.numberOfLines = 0
        var text = ""
        for i in 1...100 {
            text += "\(i)\n"
        }
        label.text = text
        label.backgroundColor = .lightGray
    }
    
    var containerScrollViewOffsetY: CGFloat {
        return containerScrollView.convert(containerScrollView.bounds, to: rootScrollView).minY
    }
    
    func canRootScrollViewScroll() -> Bool {
        return containerScrollView.contentOffset.y == 0
    }
    
    func canContainerScrollViewScroll() -> Bool {
        return rootScrollView.contentOffset.y >= containerScrollViewOffsetY
    }
}

extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView === rootScrollView {
            guard !canRootScrollViewScroll() else {
                return
            }
            // 4. scrollView 不能滚动,保持 contentOffset 不变
            scrollView.contentOffset.y = containerScrollViewOffsetY
        } else if scrollView === containerScrollView {
            guard !canContainerScrollViewScroll() else {
                return
            }
            // 3. containerScrollView 不能滚动,保持 contentOffset 不变
            containerScrollView.contentOffset.y = 0
        }
    }
}

class MyScrollView: UIScrollView, UIGestureRecognizerDelegate {
    // 1. 设置 rootScrollView 和 containerScrollView 可以同时滚动
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

你可能感兴趣的:(iOS 吸顶,双 ScrollView 处理)