iOS嵌套scrollView联动加顶部悬浮效果

先上效果图:


效果图

原理:

1、主视图是一个scrollView
2、scrollView上添加一个header(随scrollView滚动)、sectionHeader(悬浮在顶部)、内容视图contentView(与主scrollView联动)
3、contentView也是一个scrollView,用于存放子视图,子视图为多个scrollView
类似下图:


布局示意图

实现方案:

主视图:

1、使用代理方法获取header、sectionHeader、contentViews,以及各自的高度

/// 资源代理
protocol LGNestViewDataSource: NSObjectProtocol {
    /// 头部视图
    func LGNestViewHeaderView() -> UIView
    /// 头部悬浮视图
    func LGNestViewSectionHeaderView() -> UIView
    /// 头部视图高度
    func LGNestViewHeaderViewHeight() -> CGFloat
    /// 头部悬浮视图高度
    func LGNestViewSectionHeaderViewHeight() -> CGFloat
    /// 内容视图
    func LGNestViewContentViews() -> [LGNestContentView]
}

代理方法回调scrollView滚动距离

/// 视图滚动代理方法
protocol LGNestViewDelegate: NSObjectProtocol {
    /// offset 视图滚动距离
    func scrollViewDidScrolling(offset: CGFloat)
}

2、UI布局

/// UI布局
    private func layoutUI() {
        addSubview(scrollView)
        scrollView.frame = self.bounds
        
        if let dataSource = dataSource {
            headerView = dataSource.LGNestViewHeaderView()
            sectionHeaderView = dataSource.LGNestViewSectionHeaderView()
            
            headerHeight = dataSource.LGNestViewHeaderViewHeight()
            /// 防止headerView高度过小 - 高度小于导航栏高度就没有意义这样做了
            if headerHeight < kLGStatusHeight + kLGNavigationBarHeight {
                headerHeight = kLGStatusHeight + kLGNavigationBarHeight
            }
            sectionHeaderHeight = dataSource.LGNestViewSectionHeaderViewHeight()
            
            scrollView.addSubview(headerView!)
            scrollView.addSubview(sectionHeaderView!)
            scrollView.addSubview(contentScrollView)
            
            headerView!.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: headerHeight)
            contentScrollView.frame = CGRect(x: 0, y: headerHeight + sectionHeaderHeight, width: self.bounds.width, height: kLGScreenHeight)
            sectionHeaderView!.frame = CGRect(x: 0, y: headerHeight, width: self.bounds.width, height: sectionHeaderHeight)
            
            for (index, view) in dataSource.LGNestViewContentViews().enumerated() {
                contentScrollView.addSubview(view)
                view.delegate = self
                /// 设置子视图frame
                /// 高度 = 屏幕高度 - 导航栏高度 - 悬浮视图高度
                view.frame = CGRect(x: CGFloat(index) * self.bounds.width, y: 0, width: self.bounds.width, height: kLGScreenHeight - kLGStatusHeight - kLGNavigationBarHeight - sectionHeaderHeight)
            }
            
            /// 设置scrollViewcontentSize
            /// 屏幕高度 + header高度 - 导航栏高度
            scrollView.contentSize = CGSize(width: 0, height: self.bounds.height + headerHeight - kLGStatusHeight - kLGNavigationBarHeight)
            
            contentScrollView.contentSize = CGSize(width: CGFloat(dataSource.LGNestViewContentViews().count) * self.bounds.width, height: 0)
        }
    }

3、主视图与子视图联动处理

extension LGNestView: LGNestProtocol {
    
    /// 处理子scrollView滚动时,主scrollView联动
    /// - Parameters:
    ///   - scrollView: 滚动的scrollView
    ///   - position: 滚动方向
    ///   - offset: 滚动距离
    func scrollViewDidScroll(scrollView: UIScrollView, position: LGScrollPosition, offset: CGFloat) {
        var scrollViewY = self.scrollView.contentOffset.y
        if position == .down {
            scrollViewY -= offset
        } else {
            scrollViewY += offset
        }
        
        if scrollViewY > headerHeight - kLGStatusHeight - kLGNavigationBarHeight {
            scrollViewY = headerHeight - kLGStatusHeight - kLGNavigationBarHeight
        } else if scrollViewY <= 0 {
            scrollViewY = 0
        }
        
        self.scrollView.setContentOffset(CGPoint(x: 0, y: scrollViewY), animated: false)
    }
}

注:这里处理的效果是子视图向上或向下滚动时悬浮视图立即响应,比如:悬浮窗口已经在最上方时,子视图只要向下滚动悬浮窗口也会立即向下滚动,而不是等子视图的contentOffset.y为0时才向下滚动。

4、处理悬浮视图
悬浮视图的处理放在主视图代理方法scrollViewDidScroll中,并且将滚动的偏移量contentOffset.y回调给控制器,方便处理导航栏隐藏和显示等。

extension LGNestView: UIScrollViewDelegate {
    
    /// 设置悬浮视图坐标
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let scrollViewY = self.scrollView.contentOffset.y
        var sectionY: CGFloat = sectionHeaderView?.frame.origin.y ?? headerHeight
        
        if scrollViewY >= headerHeight - kLGStatusHeight - kLGNavigationBarHeight {
            sectionY = scrollViewY + kLGStatusHeight + kLGNavigationBarHeight
        } else {
            sectionY = headerHeight
        }
        sectionHeaderView?.frame = CGRect(x: 0, y: sectionY, width: self.bounds.width, height: sectionHeaderHeight)
        
        delegate?.scrollViewDidScrolling(offset: scrollViewY)
    }
}
子视图:

1、使用代理方法回调子视图滚动处理结果

enum LGScrollPosition {
    case up
    case down
}

protocol LGNestProtocol: NSObjectProtocol {
    /// 视图滚动回调
    /// - Parameters:
    ///   - scrollVIew: 滚动的scrollView
    ///   - position: 滚动方向
    ///   - offset: 滚动距离
    func scrollViewDidScroll(scrollView: UIScrollView, position: LGScrollPosition, offset: CGFloat)
}

2、定义一个基类处理所有子视图滚动事件

class LGNestContentView: UIView {
    weak var delegate: LGNestProtocol?
    /// scrollView的contentOffset.y最后的值 - 用于判断滚动方向
    private var lastOffsetY: CGFloat = 0
    /// scrollView最后的滚动方向 - 用于处理减速回弹效果
    private var lastPosition: LGScrollPosition = .up
    
    /// 处理scrollView滚动 - 子类需要在scrollView的delegate方法"scrollViewDidScroll"中调用该方法
    fileprivate func viewDidScrolling(scrollView: UIScrollView) {
        
        /// y轴偏移量
        let y = scrollView.contentOffset.y
        
        /// 获取最后滚动的方向
        var position: LGScrollPosition = lastPosition
        /// 当手指离开屏幕后及回弹效果发生时,防止方向改变
        if scrollView.isDragging && !scrollView.isDecelerating {
            if y < lastOffsetY {
                position = .down
            } else {
                position = .up
            }
        }
        
        lastPosition = position
        
        /// 滚动距离
        let distance = CGFloat(fabsf(fabsf(Float(y)) - fabsf(Float(lastOffsetY))))
        /// 代理传值
        delegate?.scrollViewDidScroll(scrollView: scrollView, position: position, offset: distance)
        lastOffsetY = y
    }
}

3、子视图只需要继承自这个基类,并在scrollView、tableView、collectionView代理方法scrollViewDidScroll中调用父类的viewDidScrolling方法即可。

class FirstView: LGNestContentView {
}
extension FirstView: UITableViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        viewDidScrolling(scrollView: scrollView)
    }
}

结语:

为了做这个效果,网上也看了别人的一些方案,写的都比较复杂,当然效果也很好。这个做的比较简陋除了高度计算繁琐一些,其他的都没啥难度,别人的都是使用collectionView来做内容视图,这样可以复用子视图,而我没有想到,写完了才想到这个点,so sad~
就这样吧,下雨天打孩子 —— 反正闲着也是闲着,就当练练手了。

Just do IT~

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

推荐阅读更多精彩内容