iOS ScrollView嵌套ScrolloView解决方案 - Swift

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/glt_code/article/details/78576628

iOS ScrollView嵌套ScrolloView解决方案 - Swift





1. 基础版实现思路

1.1:层次结构

底部是一个UITableView,上面黄色部分为tableView的tableHeaderView,cell的数量为1,cell的contentView上防止了一个LTPageView, pageView上放置了一个scrollView且可以左右滑动分页,scrollView上放置控制器view,控制器view上放置各自的scrollView(tableView或collectionView)

1.2:使用方法(具体查看Demo)

(1)创建LTSimpleMabager,并添加到视图,传入frame,子控制器数组,标题数组,当前的控制器以及pageView的样式设置

LTSimpleManager(frame: <#T##CGRect#>, viewControllers: <#T##[UIViewController]#>, titles: <#T##[String]#>, currentViewController: <#T##UIViewController#>, layout: <#T##LTLayout#>)

(2)初始化子控制器的scrollView(tableView或collectionView),且tableView的y值从pageTitleView的高度开始,Demo中为44,具体可根据产品需求而定,tableView的height则为父view的高减去44

(3)将子控制器的scrollView(tableView或collectionView)赋值给glt_scollView即glt_scrollView = tableView,以下会说明原因。

1.3:实现思路

(1)当滑动底部tableView的时候,当tableView的contentOffset.y 小于 header的高的时候,将内容ScrollView的contentOffset设置为.zero

private func contentScrollViewScrollConfig(_ viewController: UIViewController) {
        viewController.glt_scrollView?.scrollHandle = {[weak self] scrollView in
            guard let `self` = self else { return }
            self.contentTableView = scrollView
            if self.tableView.contentOffset.y < self.kHeaderHeight {
                scrollView.contentOffset = .zero;
                scrollView.showsVerticalScrollIndicator = false
            }else{
                scrollView.showsVerticalScrollIndicator = true
            }
        }
    }

(2)当滑动内容ScrollView的时候, 当内容contentOffset.y 大于 0(说明滑动的是内容ScrollView) 或者 当底部tableview的contentOffset.y大于 header的高度的时候,将底部tableView的偏移量设置为kHeaderHeight, 并将其他的scrollView的contentOffset置为.zero

public func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard scrollView == tableView, let contentTableView = contentTableView else { return }
        let offsetY = scrollView.contentOffset.y
        if contentTableView.contentOffset.y > 0.0 || offsetY > kHeaderHeight {
            tableView.contentOffset = CGPoint(x: 0.0, y: kHeaderHeight)
        }
        if scrollView.contentOffset.y < kHeaderHeight {
            for viewController in viewControllers {
                guard viewController.glt_scrollView != scrollView else { continue }
                viewController.glt_scrollView?.contentOffset = .zero
            }
        }
    }

(3)headerView添加以及各个点击事件回调处理。

simpleManager.configHeaderView {[weak self] in
            guard let strongSelf = self else { return nil }
            let headerView = strongSelf.testLabel()
            return headerView
        }
simpleManager.didSelectIndexHandle { (index) in
           
        }
simpleManager.refreshTableViewHandle { (scrollView, index) in
            scrollView.mj_header = MJRefreshNormalHeader {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: {
                    scrollView.mj_header.endRefreshing()
                })
            }
        }

2.进阶版实现思路

2.1:层次结构

底部是LTPageView, pageView上放置了一个scrollView且可以左右滑动分页,scrollView上放置控制器view,headerView是一个单独的View在顶部,利用scrollView的contentInset将其放置在了最上面

2.2:使用方法(具体查看Demo)

(1)创建LTAdvancedManager,并添加到视图,传入frame,子控制器数组,标题数组,当前的控制器以及pageView的样式设置和自己的headerView(在闭包中返回即可)

LTAdvancedManager(frame: <#T##CGRect#>, viewControllers: <#T##[UIViewController]#>, titles: <#T##[String]#>, currentViewController: <#T##UIViewController#>, layout: <#T##LTLayout#>, headerViewHandle: <#T##() -> UIView#>)

(2)初始化子控制器的scrollView(tableView或collectionView),且tableView的y值从0开始,tableView的height则为父view的高减去44

(3)将子控制器的的scrollView(tableView或collectionView)赋值给glt_scollView即glt_scrollView = tableView,以下会说明原因。

2.3:实现思路

(1)主要是利用子控制的滚动来控制headerView

//MARK: 当前控制器的滑动方法事件处理 
    private func contentScrollViewDidScroll(_ contentScrollView: UIScrollView, _ absOffset: CGFloat)  {
        
        //获取当前控制器
        let currentVc = viewControllers[currentSelectIndex]
        
        //外部监听当前ScrollView的偏移量
        self.delegate?.glt_scrollViewOffsetY?((currentVc.glt_scrollView?.contentOffset.y ?? kHeaderHeight) + self.kHeaderHeight + layout.sliderHeight)
        
        //获取偏移量
        let offsetY = contentScrollView.contentOffset.y
        
        //获取当前pageTitleView的Y值
        var pageTitleViewY = pageView.pageTitleView.frame.origin.y
        
        //pageTitleView从初始位置上升的距离
        let titleViewBottomDistance = offsetY + kHeaderHeight + layout.sliderHeight
        
        let headerViewOffset = titleViewBottomDistance + pageTitleViewY
        
        if absOffset > 0 && titleViewBottomDistance > 0 {//向上滑动
            if headerViewOffset >= kHeaderHeight {
                pageTitleViewY += -absOffset
                if pageTitleViewY <= hoverY {
                    pageTitleViewY = hoverY
                }
            }
        }else{//向下滑动
            if headerViewOffset < kHeaderHeight {
                pageTitleViewY = -titleViewBottomDistance + kHeaderHeight
                if pageTitleViewY >= kHeaderHeight {
                    pageTitleViewY = kHeaderHeight
                }
            }
        }
        
        pageView.pageTitleView.frame.origin.y = pageTitleViewY
        headerView?.frame.origin.y = pageTitleViewY - kHeaderHeight
        let lastDiffTitleToNavOffset = pageTitleViewY - lastDiffTitleToNav
        lastDiffTitleToNav = pageTitleViewY
        //使其他控制器跟随改变
        for subVC in viewControllers {
            guard subVC != currentVc else { continue }
            guard let vcGlt_scrollView = subVC.glt_scrollView else { continue }
            vcGlt_scrollView.contentOffset.y += (-lastDiffTitleToNavOffset)
            subVC.glt_upOffset = String(describing: vcGlt_scrollView.contentOffset.y)
        }
    }

3.问题以及补充

3.1:问题1:全局监听scrollView的滚动

实现思路是根据runtime,交换方法的实现,主要的坑就是当时采用的交换scrollViewDidScroll(_:),但它是UIScrollViewDelegate的协议方法,发现行不通,后来找到了UIScrollView中的一个方法Selector(("_notifyDidScroll")),发现拦截它是可行的,当然Swift中拦截方法,也是有很多坑的,和OC有很大不同,具体操作看源码吧!

3.2:进阶版遗留小问题

当headerView中有按钮需要响应事件的时候,我这里采用的是利用override func point(inside point: CGPoint, with event: UIEvent?) -> Bool方法进行判断,因为上面headerView的交互只有关闭了才能滑动headerView左右切换,所以在这里我只允许要响应事件的控件交互打开了,其他区域交互关闭。如果有更好的思路欢迎联系!!!不胜感激!

//MARK: 暂用,待优化。
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        for tempView in self.subviews {
            if tempView.isKind(of: UILabel.self) {
                let button = tempView as! UILabel
                let newPoint = self.convert(point, to: button)
                if button.bounds.contains(newPoint) {
                    return true
                }
            }
        }
        return false
    }


猜你喜欢

转载自blog.csdn.net/glt_code/article/details/78576628
0条评论
添加一条新回复