CLStickyPage 是一个 iOS 吸顶分页容器,解决 Header + Sticky Tab + Horizontal Pages + Nested Scroll 场景。
支持三种容器:
CLStickyPageView:纯UIView页面CLStickyControllerPageView:纯UIViewController页面CLStickyView:统一容器,可纯 View、可纯 Controller,也可混合
| 方案 | 组件 | 页面返回类型 | 何时选 |
|---|---|---|---|
| View 模式 | CLStickyPageView |
CLStickyViewPage |
页面是轻量视图,追求最小成本 |
| Controller 模式 | CLStickyControllerPageView |
CLStickyControllerPage |
每页需要独立生命周期、路由、状态 |
| 统一容器模式 | CLStickyView |
CLStickyPage(可返回 View/Controller) |
你想用一套容器覆盖“纯”和“混合”两类需求 |
public protocol CLStickyPage: AnyObject {
var scrollView: UIScrollView { get }
}
public protocol CLStickyViewPage: CLStickyPage where Self: UIView {}
public protocol CLStickyControllerPage: CLStickyPage where Self: UIViewController {}.package(url: "https://github.com/JmoVxia/CLStickyPage.git", from: "1.0.0")dependencies: ["CLStickyPage"]pod 'CLStickyPage', '~> 1.0'final class DemoViewController: UIViewController {
private lazy var pageView: CLStickyPageView = {
let view = CLStickyPageView(isLazyLoading: false)
view.dataSource = self
view.delegate = self
view.hoverStickyTopInset = 40
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(pageView)
pageView.frame = view.bounds
pageView.headerView = headerView
pageView.hoverView = segmentedBar
pageView.reload()
}
}
extension DemoViewController: CLStickyPageViewDataSource {
func numberOfPages(in pageView: CLStickyPageView) -> Int { 4 }
func pageView(_ pageView: CLStickyPageView, pageAt index: Int) -> CLStickyViewPage {
CLDemoStickyPageView()
}
}final class DemoViewController: UIViewController {
private lazy var pageView: CLStickyControllerPageView = {
let view = CLStickyControllerPageView(isLazyLoading: true)
view.dataSource = self
view.delegate = self
view.hoverStickyTopInset = 40
return view
}()
}
extension DemoViewController: CLStickyControllerPageViewDataSource {
func numberOfPages(in controllerPageView: CLStickyControllerPageView) -> Int { 4 }
func controllerPageView(
_ controllerPageView: CLStickyControllerPageView,
pageControllerAt index: Int
) -> CLStickyControllerPage {
CLDemoTablePageController(title: "Page \(index)", accentColor: .systemBlue)
}
func parentViewController(for controllerPageView: CLStickyControllerPageView) -> UIViewController {
self
}
}CLStickyView 的关键点是:pageAt 返回 CLStickyPage,所以你可以自由决定每一页返回 View 还是 Controller。
enum Mode {
case viewOnly
case controllerOnly
case mixed
}
final class DemoViewController: UIViewController {
var mode: Mode = .mixed
private var viewCache: [Int: CLDemoStickyPageView] = [:]
private var controllerCache: [Int: CLDemoTablePageController] = [:]
private lazy var stickyView: CLStickyView = {
let view = CLStickyView(isLazyLoading: true)
view.dataSource = self
view.delegate = self
return view
}()
}
extension DemoViewController: CLStickyViewDataSource {
func numberOfPages(in stickyView: CLStickyView) -> Int { 4 }
func stickyView(_ stickyView: CLStickyView, pageAt index: Int) -> CLStickyPage {
switch mode {
case .viewOnly:
return viewCache[index] ?? {
let page = CLDemoStickyPageView()
viewCache[index] = page
return page
}()
case .controllerOnly:
return controllerCache[index] ?? {
let page = CLDemoTablePageController(title: "Page \(index)", accentColor: .systemGreen)
controllerCache[index] = page
return page
}()
case .mixed:
if index % 2 == 0 {
return viewCache[index] ?? {
let page = CLDemoStickyPageView()
viewCache[index] = page
return page
}()
} else {
return controllerCache[index] ?? {
let page = CLDemoTablePageController(title: "Page \(index)", accentColor: .systemOrange)
controllerCache[index] = page
return page
}()
}
}
}
func parentViewController(for stickyView: CLStickyView) -> UIViewController? {
self
}
}说明:
- 纯 View 时,
parentViewController可不实现(默认nil)。 - 纯 Controller 或混合(包含 Controller 页)时,必须返回有效的父控制器。
三种容器都支持以下核心能力:
- 布局相关:
headerView、hoverView、backgroundView、hoverStickyTopInset - 交互相关:
showIndicator、isHorizontalScrollEnabled - 状态相关:
currentPageIndex、numberOfPages - 操作相关:
reload(toPageAt:)、scrollToPage(at:animated:)
CLStickyView 额外提供:
currentPage:当前页(CLStickyPage)currentController:当前控制器页(如果当前页是 Controller)page(at:):获取指定页对象(懒加载时只对已创建页面生效)
- 入口:
CLStickyPage/Demo/CLViewController.swift - View 模式:
CLStickyPage/Demo/ViewMode/CLViewModeController.swift - Controller 模式:
CLStickyPage/Demo/ControllerMode/CLControllerModeController.swift - 统一容器模式:
CLStickyPage/Demo/MixedMode/CLMixedModeController.swift
统一容器 Demo 已内置三种组合切换:
- 纯 View
- 纯 Controller
- 混合(View + Controller)
- 子页面的
scrollView建议contentInsetAdjustmentBehavior = .never,避免系统 inset 干扰吸顶计算。 reload(toPageAt:)会重新获取页面,若你需要状态复用,请做好缓存。- 懒加载模式下,
page(at:)对尚未创建的索引会返回nil,这是预期行为。
MIT