SkillAgentSearch skills...

NestedPageViewController

iOS嵌套联动分页控制器

Install / Use

/learn @SPStore/NestedPageViewController

README

<p align="center"> <img src="https://raw.githubusercontent.com/SPStore/NestedPageViewController/master/Assets/NestedPageViewController_logo.png" title="NestedPageViewController logo" width="480"> </p> <p align="center"> 一个用于iOS的嵌套页面视图控制器,提供平滑的滚动协调体验。 </p> <p align="center"> <a href="https://github.com/SPStore/NestedPageViewController"><img src="https://img.shields.io/badge/platform-iOS-blue.svg"></a> <a href="https://swift.org/"><img src="https://img.shields.io/badge/Swift-5.0-orange.svg"></a> <a href="https://developer.apple.com/ios/"><img src="https://img.shields.io/badge/iOS-13.0%2B-blue.svg"></a> <a href="https://github.com/SPStore/NestedPageViewController/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg"></a> <a href="https://cocoapods.org/pods/NestedPageViewController"><img src="https://img.shields.io/badge/pod-v2.0.0-brightgreen.svg"></a> <a href="https://swift.org/package-manager/"><img src="https://img.shields.io/badge/SPM-compatible-brightgreen.svg"></a> </p>

功能特点

  • [x] 支持头部视图、标签栏和多个子视图控制器
  • [x] 支持内容滚动位置记录(该功能是设计本框架的最大动力来源)
  • [x] 支持局部刷新和全局刷新
  • [x] 支持子页面预加载(默认是滑动到指定页才加载)
  • [x] 支持头部视图手指拖拽滚动并带动整体,可配置控制内容scrollView是否惯性滚动
  • [x] 支持自定义标签栏
  • [x] 支持旋转
  • [x] 更多细节和功能请下载demo

功能演示

<table> <tr bgcolor="#f2f2f2"> <td width="250" align="center"><strong>记录滚动位置</strong></td> <td width="250" align="center"><strong>局部刷新</strong></td> <td width="250" align="center"><strong>全局刷新</strong></td> </tr> <tr> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/记录滚动位置.gif" width="250" alt="记录滚动位置"></td> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/局部刷新.gif" width="250" alt="局部刷新"></td> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/全局刷新.gif" width="250" alt="全局刷新"></td> </tr> <tr bgcolor="#f2f2f2"> <td align="center"><strong>头部始终固定不动</strong></td> <td align="center"><strong>头部缩放+导航栏隐藏</strong></td> <td align="center"><strong>显示底部tabBar</strong></td> </tr> <tr> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/头部始终固定不动.gif" width="250" alt="头部始终固定不动"></td> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/头部缩放+隐藏导航栏.gif" width="250" alt="头部缩放+隐藏导航栏"></td> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/显示系统tabBar.gif" width="250" alt="显示底部tabBar"></td> </tr> </tr> <tr bgcolor="#f2f2f2"> <td align="center"><strong>子VC的sectionHeader吸顶</strong></td> <td align="center"><strong>运行时修改头部高度</strong></td> <td align="center"><strong>没有头部</strong></td> </tr> <tr> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/子VC的sectionHeader吸顶.gif" width="250" alt="子VC的sectionHeader吸顶"></td> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/运行时修改头部高度.gif" width="250" alt="运行时修改头部高度"></td> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/没有头部.gif" width="250" alt="没有头部"></td> </tr> <tr bgcolor="#f2f2f2"> <td align="center"><strong>滚到顶部</strong></td> <td align="center"><strong>自定义标签栏1</strong></td> <td align="center"><strong>自定义标签栏2</strong></td> </tr> <tr> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/滚到顶部.gif" width="250" alt="滚到顶部"></td> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/自定义标签栏1.gif" width="250" alt="自定义标签栏1"></td> <td><img src="https://github.com/SPStore/SPExmapleResurces/blob/main/NestedPageViewController/自定义标签栏2.gif" width="250" alt="自定义标签栏2"></td> </table>

系统要求

  • iOS 13.0+
  • Swift 5.0+

安装

Swift Package Manager

在Xcode中,选择 File > Swift Packages > Add Package Dependency,然后输入以下URL:

https://github.com/SPStore/NestedPageViewController.git

CocoaPods

在你的Podfile中添加:

pod 'NestedPageViewController'

然后运行:

pod install

注意:如果CocoaPods的方式安装,编译报错:Xcode error when building app: line 7: /resources-to-copy-Project.txt: Permission denied,或者其他类似权限问题,请在你的主工程中的Targets -> Build Settings -> User Script Sandboxing 改为No

使用方法

NestedPageViewController提供两种使用方式:添加子控制器方式和继承方式。

方式一:添加子控制器方式

import UIKit
import NestedPageViewController

class YourViewController: UIViewController {
    
    // MARK: - Properties
    
    private var nestedPageViewController = NestedPageViewController()
    private var coverView = YourHeaderView()
    private var customTabStrip = YourCustomTabStrip()
    
    // MARK: - View Controllers
    
    private let childControllerTitles = ["标签一", "标签二", "标签三", "标签四"]
    
    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
                
        setupNestedPageViewController()
    }
    
    // MARK: - Setup
    
    private func setupNestedPageViewController() {
        nestedPageViewController.dataSource = self
        nestedPageViewController.delegate = self
        
        // 添加为子控制器
        addChild(nestedPageViewController)
        view.addSubview(nestedPageViewController.view)
        nestedPageViewController.didMove(toParent: self)
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        // 更新NestedPageViewController的frame
        let safeAreaTop = view.safeAreaInsets.top
        nestedPageViewController.view.frame = CGRect(
            x: 0,
            y: safeAreaTop,
            width: view.bounds.width,
            height: view.bounds.height - safeAreaTop
        )
    }
}

// MARK: - NestedPageViewControllerDataSource

extension YourViewController: NestedPageViewControllerDataSource {
    
    func numberOfViewControllers(in pageViewController: NestedPageViewController) -> Int {
        return childControllerTitles.count
    }
    
    func pageViewController(_ pageViewController: NestedPageViewController, viewControllerAt index: Int) -> NestedPageScrollable? {
        guard index >= 0 && index < childControllerTitles.count else { return nil }
        
        switch index {
        case 0:
            return YourChildViewController1()  // 必须遵守NestedPageScrollable协议
        case 1:
            return YourChildViewController2()  // 必须遵守NestedPageScrollable协议
        case 2:
            return YourChildViewController3()  // 必须遵守NestedPageScrollable协议
        case 3:
            return YourChildViewController4()  // 必须遵守NestedPageScrollable协议
        default:
            return nil
        }
    }
    
    func coverView(in pageViewController: NestedPageViewController) -> UIView? {
        return coverView
    }
    
    func heightForCoverView(in pageViewController: NestedPageViewController) -> CGFloat {
        return 200.0
    }
    
    func tabStrip(in pageViewController: NestedPageViewController) -> UIView? {
        return customTabStrip  // 使用自定义标签栏
    }
    
    func heightForTabStrip(in pageViewController: NestedPageViewController) -> CGFloat {
        return 50.0
    }
    
    func titlesForTabStrip(in pageViewController: NestedPageViewController) -> [String]? {
        return nil  // 使用自定义标签栏时返回nil
    }
}

// MARK: - NestedPageViewControllerDelegate

extension YourViewController: NestedPageViewControllerDelegate {
    
    // 页面横向滚动到指定索引位置的回调方法
    func pageViewController(_ pageViewController: NestedPageViewController, didScrollToPageAt index: Int) {
        // 页面切换回调
        print("当前页面索引: \(index)")
    }
    
    // 内容垂直滚动视图的滚动状态变化回调方法
    func pageViewController(_ pageViewController: NestedPageViewController, contentScrollViewDidScroll scrollView: UIScrollView, headerOffset: CGFloat, isSticked: Bool) {
        // headerOffset: 头部相对contentScrollView顶部的偏移量
        // isSticked: 是否处于完全吸顶状态
        
        // 例如:根据滚动状态控制导航栏的显示/隐藏
        if isSticked {
            // 头部完全吸顶,可以显示导航栏标题
        } else {
            // 头部未完全吸顶,可以隐藏导航栏标题
        }
    }
}

方式二:继承方式

import UIKit
import NestedPageViewController

class YourNestedPageViewController: NestedPageViewController {
    
    // MARK: - Properties
    
    private var coverView = YourHeaderView()
    private var customTabStrip = YourCustomTabStrip()
    
    // MARK: - View Controllers
    
    private let childControllerTitles = ["标签一", "标签二", "标签三", "标签四"]

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupNestedPageViewController()
    }
    
    override func viewDidLayoutSubviews() {
        let safeTop = view.safeAreaInsets.top
        containerInsets = UIEdgeInsets(top: safeTop, left: 0, bottom: 0, right: 0)
        
        // 采用继承方式时,需要在super之前设置containerInsets
        super.viewDidLayoutSubviews()
    }
    
    // MARK: - Setup
    
    private func setupNestedPageViewController() {
        // 设置数据源
        dataSource = self
        
        // 设置代理(继承方式下,可以直接重写代理方法)
        delegate = self
    }
    
    // MARK: - NestedPageViewControllerDelegate
    
    // 页面横向滚动到指定索引位置的回调方法
    override func pageViewController(_ pageViewController: NestedPageViewController, didScrollToPageAt index: Int) {
        super.pageViewController(pageViewController, didScrollToPageAt: index)
        
        // 页面切换回调
        print("当前页面索引: \(index)")
    }
    
    // 内容垂直滚动视图的滚动状态变化回调方法
    override func pageViewController(_ pageViewController: NestedPageViewController, contentScrollViewDidScroll scrollView: UIScrollView, headerOffset: CGFloat, isSticked: Bool) {
        super.pageViewController(pageViewController, contentScrollViewDidScroll: scrollView, headerOffset: headerOffset, isSticked: isSticked)
        
        // headerOffset: 头部相对contentScrollView顶部的偏移量
        // isSticked: 是否处于完全吸顶状态
        
        // 例如:根据滚动状态控制导航栏的显示/隐藏
        if isSticked 
View on GitHub
GitHub Stars614
CategoryDevelopment
Updated3d ago
Forks107

Languages

Swift

Security Score

95/100

Audited on Mar 30, 2026

No findings