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
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容