NestedPageViewController
一个用于 iOS 的嵌套页面视图控制器,提供平滑的滚动协调体验。
平台 iOS | Swift 5.0 | iOS 13.0+ | MIT License | CocoaPods v1.0.0 | SPM Compatible
功能特点
- 支持头部视图、标签栏和多个子视图控制器
- 支持内容滚动位置记录
- 支持局部刷新和全局刷新
- 支持子页面预加载
- 支持头部视图拖拽滚动并带动整体
- 支持自定义标签栏
- 支持旋转
- 更多功能请下载demo
功能演示
| 记录滚动位置 | 局部刷新 | 全局刷新 |
|---|---|---|
![]() 记录滚动位置
|
![]() 局部刷新
|
![]() 全局刷新
|
| 头部始终固定不动 | 头部缩放+隐藏导航栏 | 显示系统 tabBar |
|---|---|---|
![]() 头部始终固定不动
|
![]() 头部缩放+隐藏导航栏
|
![]() 显示系统tabBar
|
| 滚到顶部 | 自定义标签栏1 | 自定义标签栏2 |
|---|---|---|
![]() 滚到顶部
|
![]() 自定义标签栏1
|
![]() 自定义标签栏2
|
系统要求
- iOS 13.0+
- Swift 5.0+
安装
Swift Package Manager
[https://github.com/SPStore/NestedPageViewController.git](https://github.com/SPStore/NestedPageViewController.git)
CocoaPods
pod 'NestedPageViewController'
运行:
pod install
注意:如果 CocoaPods 编译报错,请在主工程 Targets -> Build Settings -> User Script Sandboxing 改为 No
使用方法
方式一:添加子控制器方式
import UIKit
import NestedPageViewController
class YourViewController: UIViewController {
private var nestedPageViewController = NestedPageViewController()
private var coverView = YourHeaderView()
private var customTabStrip = YourCustomTabStrip()
private let childControllerTitles = ["标签一", "标签二", "标签三", "标签四"]
override func viewDidLoad() {
super.viewDidLoad()
setupNestedPageViewController()
}
private func setupNestedPageViewController() {
nestedPageViewController.dataSource = self
nestedPageViewController.delegate = self
addChild(nestedPageViewController)
view.addSubview(nestedPageViewController.view)
nestedPageViewController.didMove(toParent: self)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let safeAreaTop = view.safeAreaInsets.top
nestedPageViewController.view.frame = CGRect(
x: 0, y: safeAreaTop,
width: view.bounds.width,
height: view.bounds.height - safeAreaTop
)
}
}
extension YourViewController: NestedPageViewControllerDataSource {
func numberOfViewControllers(in pageViewController: NestedPageViewController) -> Int {
childControllerTitles.count
}
func pageViewController(_ pageViewController: NestedPageViewController, viewControllerAt index: Int) -> (UIViewController & NestedPageScrollable)? {
switch index {
case 0: return YourChildViewController1()
case 1: return YourChildViewController2()
case 2: return YourChildViewController3()
case 3: return YourChildViewController4()
default: return nil
}
}
func coverView(in pageViewController: NestedPageViewController) -> UIView? { coverView }
func heightForCoverView(in pageViewController: NestedPageViewController) -> CGFloat { 200 }
func tabStrip(in pageViewController: NestedPageViewController) -> UIView? { customTabStrip }
func heightForTabStrip(in pageViewController: NestedPageViewController) -> CGFloat { 50 }
func titlesForTabStrip(in pageViewController: NestedPageViewController) -> [String]? { nil }
}
extension YourViewController: NestedPageViewControllerDelegate {
func pageViewController(_ pageViewController: NestedPageViewController, didScrollToPageAt index: Int) {
print("当前页面索引: \(index)")
}
func pageViewController(_ pageViewController: NestedPageViewController, contentScrollViewDidScroll scrollView: UIScrollView, headerOffset: CGFloat, isSticked: Bool) {
if isSticked { /* 头部完全吸顶 */ }
else { /* 头部未吸顶 */ }
}
}
方式二:继承方式
import UIKit
import NestedPageViewController
class YourNestedPageViewController: NestedPageViewController {
private var coverView = YourHeaderView()
private var customTabStrip = YourCustomTabStrip()
private let childControllerTitles = ["标签一", "标签二", "标签三", "标签四"]
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
}
override func viewDidLayoutSubviews() {
containerInsets = UIEdgeInsets(top: view.safeAreaInsets.top, left: 0, bottom: 0, right: 0)
super.viewDidLayoutSubviews()
}
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)
}
}
extension YourNestedPageViewController: NestedPageViewControllerDataSource {
func numberOfViewControllers(in pageViewController: NestedPageViewController) -> Int { childControllerTitles.count }
func pageViewController(_ pageViewController: NestedPageViewController, viewControllerAt index: Int) -> (UIViewController & NestedPageScrollable)? {
switch index {
case 0: return YourChildViewController1()
case 1: return YourChildViewController2()
case 2: return YourChildViewController3()
case 3: return YourChildViewController4()
default: return nil
}
}
func coverView(in pageViewController: NestedPageViewController) -> UIView? { coverView }
func heightForCoverView(in pageViewController: NestedPageViewController) -> CGFloat { 200 }
func tabStrip(in pageViewController: NestedPageViewController) -> UIView? { customTabStrip }
func heightForTabStrip(in pageViewController: NestedPageViewController) -> CGFloat { 50 }
func titlesForTabStrip(in pageViewController: NestedPageViewController) -> [String]? { nil }
}
Objective-C 使用方式
OC 工程需桥接,示例参考:Example/NestedPageExample/Examples-OC
性能报告
-
[内存占用]
memory.png -
[CPU 使用率]
cpu.png
实现原理
项目起源
前身是 8 年前开发的 HVScrollView 项目,灵感来自腾讯 Bugly 发布的特斯拉组件文章。
时光荏苒,8年过去了,我积累了更多的开发经验和技术沉淀,现在将这个想法重新实现并开源,希望能为iOS开发社区提供一个更加完善、易用的嵌套滚动解决方案。NestedPageViewController在保留原有思想精髓的基础上,进一步优化了性能和用户体验,为现代iOS应用提供了更加流畅的页面嵌套滚动效果。










