FSCalendar 源码解析(一):整体架构与核心组件深度剖析
【免费下载链接】FSCalendarA fully customizable iOS calendar library, compatible with Objective-C and Swift项目地址: https://gitcode.com/gh_mirrors/fs/FSCalendar
FSCalendar 是一款高度可定制的 iOS 日历控件库,同时支持 Objective-C 与 Swift,被超过 10,000 个 App 采用。本文带你深入 FSCalendar 源码,剖析它的整体架构、核心组件职责与经典设计模式,帮助你在 10 分钟内看懂这个日历库"怎么搭起来的"。
📌核心关键词:FSCalendar 源码解析、iOS 日历控件架构、CalendarView 核心组件
🧭 一分钟了解 FSCalendar
FSCalendar 本质上是一个"伪装成 UIView 的日历容器",它内部组合了以下5 类核心组件:
| 组件 | 文件 | 一句话职责 |
|---|---|---|
| 主控制器 | FSCalendar.m | 组装视图树、处理选择/滚动/翻页逻辑 |
| 渲染引擎 | FSCalendarCollectionViewLayout.m | 自定义 UICollectionView 布局,计算每个日期的格子位置 |
| 日期计算器 | FSCalendarCalculator.m | NSDate与IndexPath互译的"翻译官" |
| 外观中枢 | FSCalendarAppearance.m | 统一管理所有颜色、字体、偏移量 |
| 委托代理 | FSCalendarDelegationProxy.m | 让日历支持"多委托共存" |
💡 记住这张表,后面每个组件都会展开讲。
📂 目录结构导览
整个仓库非常精简,核心源码全部集中在 FSCalendar/ 目录下:
- FSCalendar/FSCalendar.h —— 对外主接口,定义了
FSCalendarDataSource、FSCalendarDelegate、FSCalendarDelegateAppearance三大协议 - FSCalendar/include/ —— 打包成 Framework 时暴露的头文件清单
- Example-Objc/ —— Objective-C 完整示例工程(DIY 日历、区间选择、范围切换等 Demo)
- Example-Swift/ —— Swift 示例工程
- Example-TodayExtension/ —— Today 小组件集成示例
- FSCalendar.podspec、Package.swift —— CocoaPods 与 SPM 两种集成方式的描述文件
核心源码只有16 对 .h/.m 文件,全部围绕"主控件 + 渲染 + 计算 + 外观 + 委托"五大职责展开,规模小但分工极清晰,非常适合做源码学习。
🏛️ 核心组件一:FSCalendar 主控制器
FSCalendar.h 中声明的FSCalendar继承自UIView,并标记了IB_DESIGNABLE,因此可以直接拖进 Interface Builder 可视化调参(见 FSCalendar (IBExtension) 中的IBInspectable属性)。
打开 FSCalendar.m 的initialize方法(约 146 行起),能看到整个日历的"装配流水线":
- 创建外观对象:
_appearance = [[FSCalendarAppearance alloc] init],并让它反向持有日历; - 创建日期工具:
NSCalendar(gregorian)+NSDateFormatter,负责所有日期运算; - 搭建视图层级:
contentView → daysContainer → collectionView,日历的"月视图"本质是嵌在容器里的一个 CollectionView; - 注册三种可复用视图:日期格子
FSCalendarCell、空白占位格FSCalendarBlankCell、吸顶头FSCalendarStickyHeader; - 挂载两个"助手":
transitionCoordinator(过渡动画协调器)和calculator(日期计算器); - 初始化两个委托代理:
dataSourceProxy与delegateProxy(由 FSCalendarDelegationFactory 生产)。
🔍关键发现:主控件还重写了setValue:forUndefinedKey:(FSCalendar.m#L261-L280),把 XIB 中无法识别的属性动态转发给 appearance 或 layout——这就是为什么在 Storyboard 里能直接配置titleDefaultColor这类"日历本体并不存在"的属性,非常巧妙。
⚙️ 核心组件二:CollectionView 与自定义 Layout
日历的"翻页"并不是真的在滚日历,而是每个 Section 代表一个月/一周,横滑切换 Section。这由两个类实现:
- FSCalendarCollectionView —— 对
UICollectionView的轻量封装,增加了一个internalDelegate内部回调通道; - FSCalendarCollectionViewLayout —— 继承
UICollectionViewLayout,是整个日历渲染的心脏,它决定:- 日期格子如何按 7 列排布;
- 月份头部(Section Header)如何"溶解"(滚动时淡出/淡入);
- 占位日期(上月/下月补位格)如何定位。
这种"UICollectionView + 自定义 Layout"的组合是 FSCalendar 高性能的关键:格子复用、按需绘制、滚动流畅,全部交给系统成熟机制。
🧮 核心组件三:FSCalendarCalculator 日期计算器
日历控件最棘手的问题是:"2026 年 9 月 18 日"和"第 3 页第 5 格"是两套坐标系,必须在它们之间反复换算。
FSCalendar 把这件事全部封装进了 FSCalendarCalculator,它提供三组"互译"接口:
- 日期 ↔ IndexPath:
dateForIndexPath:与indexPathForDate:atMonthPosition:scope:,同一个日期在不同"月份位置"(上/本/下月)和不同"视图范围"(月/周)下会算出不同的格子; - Section ↔ 页面:
monthForSection:、weekForSection:、pageForSection:,把 Section 号还原成具体月份/周; - 占位与行数:
numberOfHeadPlaceholdersForMonth:(月初需要补几个灰格子)、numberOfRowsInMonth:(这个月要占 5 行还是 6 行)。
📐 这就是典型的Calculator 模式:把纯计算逻辑从 UI 代码中剥离,FSCalendar.m中所有日期换算都委托给它,主控制器因此保持"干净"。
🎨 核心组件四:FSCalendarAppearance 外观中枢
想改日历的某个颜色,去 FSCalendarAppearance.h 就能找到对应属性:titleDefaultColor(未选中日字色)、fillSelectionColor(选中填充色)、borderRadius、weekdayFont……几乎所有视觉参数都收敛在这一个对象里。
它还定义了格子状态枚举FSCalendarCellState:
FSCalendarCellStateNormal / Selected / Placeholder / Disabled / Today / Weekend配合FSCalendarDelegateAppearance协议(定义在 FSCalendar.h#L158 起),FSCalendar 实现了"全局外观 + 按日期精细覆盖"的双层定制体系:
- 全局:直接改
calendar.appearance的属性; - 按天:在代理方法中针对特定日期返回不同颜色/圆角/偏移,例如把"工作日"和"周末"画成两种风格。
🔗 核心组件五:Delegation Proxy 多委托机制
UIView原生只允许一个 delegate,但实际开发中,父页面、弹窗管理器、数据层往往都想监听"日期选中"事件。FSCalendar 的解法是Proxy 模式:
- FSCalendarDelegationProxy 维护一个委托队列,
FSCalendar内部只和 Proxy 打交道,Proxy 再把调用按优先级分发给所有注册的委托对象; - FSCalendarDelegationFactory 用工厂方法分别产出 dataSource 版和 delegate 版代理。
结果:你随时可以追加一个 delegate(比如埋点组件),无需覆盖已有委托——这在开源日历库里是相当少见的友好设计。
🎬 辅助组件速览
| 组件 | 文件 | 作用 |
|---|---|---|
| 过渡协调器 | FSCalendarTransitionCoordinator.m | 月/周模式切换(scope 手势)时的缩放动画与布局过渡 |
| 月份标题头 | FSCalendarHeaderView.m | 显示"9月"等标题,滚动时做"溶解"效果 |
| 吸顶头 | FSCalendarStickyHeader.m | 非分页模式下,滚动到月份边界时吸附显示月份标题 |
| 星期头 | FSCalendarWeekdayView.m | 渲染"日一二三四五六",支持zh-CN等多语言 locale |
| 日期格子 | FSCalendarCell.m | 单个日期格子视图:数字、副标题、事件圆点、圆角边框 |
| 点击分发器 | FSCalendarDynamicHeader.h | 点击月份标题直接跳转该月(HeaderTouchDeliver) |
| 工具扩展 | FSCalendarExtensions.m | NSDate/NSCalendar 的便捷分类方法 |
🧩 设计模式总结
FSCalendar 的代码量不大,但模式运用得相当完整,值得逐个对照源码学习:
- 组合优于继承:FSCalendar 不继承 UIScrollView,而是组合 CollectionView,行为更可控;
- DataSource / Delegate:数据(事件数、副标题)与行为(选中回调)分离,与 UICollectionView 一脉相承,降低学习成本;
- Calculator 模式:日期与坐标系的换算独立成类;
- Proxy 模式:多委托共存;
- Coordinator 模式:
FSCalendarTransitionCoordinator把"月↔周切换"动画从主流程中剥离; - 单元格复用:基于 UICollectionView 的 Cell 复用机制保证流畅滚动。
📚源码阅读路线建议:先读 FSCalendar.m 的initialize与layoutSubviews→ 再看 FSCalendarCalculator.h 的接口设计 → 最后对照 Example-Objc/DIYExampleViewController.m 等示例验证理解。
📝 小结与下篇预告
FSCalendar 的架构可以浓缩为一句话:主控制器负责编排,Layout 负责渲染,Calculator 负责换算,Appearance 负责颜值,Proxy 负责协作。五个组件各司其职,才让一个"日期格子网格"变得既可定制又高效。
⏭️下篇预告(源码解析二):我们将深入日期选中与范围选择的状态流转、月/周 scope 切换动画的完整实现,以及 Today Extension 小组件的集成细节。
更多使用技巧可参考 MOREUSAGE.md 与 README.md。
【免费下载链接】FSCalendarA fully customizable iOS calendar library, compatible with Objective-C and Swift项目地址: https://gitcode.com/gh_mirrors/fs/FSCalendar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考