吸顶交互

最近项目中需要用到滑动吸顶交互,参考了别人的demo实现,代码未封装,适合新手,简单易懂

实现原理图层
实现图层.png
主要代码实现

外层UICollectionView,需要自定义实现gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer)方法

import UIKit

class NestedCollectionView: UICollectionView, UIGestureRecognizerDelegate {

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        if let view = otherGestureRecognizer.view, view.isKind(of: NestedCollectionView.self) {
            return true
        } else {
            if otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.self),
               let scrollView = otherGestureRecognizer.view as? UIScrollView {
                // 解决scrollView横向滚动不能与其他scrollView纵向滚动互斥的问题
                if (abs(scrollView.contentOffset.x) > 0 && abs(scrollView.contentOffset.y) == 0) { // 横向滚动
                    return false;
                }
                return true;
            }
            return false;
        }
    }

}

在外层视图控制器中,处理外层和内层手势冲突

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView.contentSize.height <= 0 { return }
        
        // 处理scrollView滑动冲突
        let contentOffsetY = scrollView.contentOffset.y
        // 吸顶临界点(此时的临界点不是视觉感官上导航栏的底部,而是当前屏幕的顶部相对scrollViewContentView的位置)
        // 如果当前控制器底部存在TabBar/ToolBar/自定义的bottomBar, 还需要减去barHeight和SAFE_AREA_INSERTS_BOTTOM的高度
        let criticalPointOffsetY = scrollView.contentSize.height - UIScreen.main.bounds.height
        
        // 利用contentOffset处理内外层scrollView的滑动冲突问题
        if contentOffsetY - criticalPointOffsetY >= 0 {
            /*
             * 到达临界点:
             * 1.未吸顶状态 -> 吸顶状态
             * 2.维持吸顶状态 (pageViewController.scrollView.contentOffsetY > 0)
             */
            cannotScroll = true
            scrollView.contentOffset = CGPoint(x: 0, y: criticalPointOffsetY)
            segmentedViewController.makePageViewControllersScrollState(canScroll: true)
        } else {
            /*
             * 未达到临界点:
             * 1.维持吸顶状态 (pageViewController.scrollView.contentOffsetY > 0)
             * 2.吸顶状态 -> 不吸顶状态
             */
            if cannotScroll {
                // “维持吸顶状态”
                scrollView.contentOffset = CGPoint(x: 0, y: criticalPointOffsetY)
            } else {
                // 吸顶状态 -> 不吸顶状态
                segmentedViewController.makePageViewControllersScrollToTop()
            }
        }
    }

内层子页面都需要继承PageListViewController控制器,处理子页面是否可以滚动

import UIKit

protocol PageListViewControllerDelegate: AnyObject {
    func pageViewControllerLeaveTop()
}

extension PageListViewController {
    static func == (lhs: PageListViewController, rhs:PageListViewController) -> Bool {
        return lhs.identifier == rhs.identifier
    }
}

class PageListViewController: UIViewController {
    public var identifier: String?
    
    public var canScroll: Bool = false {
        didSet {
            if !canScroll {
                scrollView?.contentOffset = CGPoint.zero
            }
        }
    }
    public weak var delegate: PageListViewControllerDelegate?
    
    private var scrollView: UIScrollView?

    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    public func scrollToTop() {
        scrollView?.setContentOffset(CGPoint.zero, animated: true)
    }
}

extension PageListViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        self.scrollView = scrollView
        
        if canScroll {
            if scrollView.contentOffset.y <= 0 {
                canScroll = false
                
                delegate?.pageViewControllerLeaveTop()
            }
        } else {
            canScroll = false
        }
    }
}

动图效果见demo
Demo地址:吸顶demo

参考文章:
https://www.jianshu.com/p/8b87837d9e3a

你可能感兴趣的:(吸顶交互)