- 移动开发
- UI组件
【免费下载链接】JTAppleCalendar
The Unofficial Apple iOS Swift Calendar View. Swift calendar Library. iOS calendar Control. 100% Customizable
导读
JTAppleCalendar 是 Swift 生态中一个高度可配置的日历控件,本仓库中的 README 用一句话概括了它的核心承诺——"你的日历日期单元格想长什么样,就能长什么样"(However you want them to look)。本文以 README.md 中列出的功能清单为主线,结合 Sources/JTAppleCalendar 下的源码实现与 SampleJTAppleCalendar 中的真实示例,系统讲解区间选择、边界日期、周/月模式、自定义 Cell 与月份头部、每周首日、横竖滚动、按日期滚动等能力,并给出可直接复制的代码片段与底层实现依据,帮助你快速上手并将日历深度集成进自己的 App。
一、功能总览:README 承诺了什么
仓库 README 的 Features 部分列出了以下已实现([x])的能力,本文后续章节将逐一展开:
| 功能 | 说明 | 核心源码依据 |
|---|---|---|
| 区间选择(Range selection) | 支持选择一段日期,外观完全由你设计 | JTACMonthView.swift 中的allowsRangedSelection与 CalendarEnums.swift 中的SelectionRangePosition/RangeSelectionMode |
| 边界日期(Boundary dates) | 限制日历的日期范围 | JTACMonthViewProtocols.swift 的configureCalendar与 CalendarStructs.swift 的ConfigurationParameters |
| 周/月视图(Week/month mode) | 显示 1 行周视图,或 2/3/6 行月视图 | ConfigurationParameters.numberOfRows(见 CalendarStructs.swift) |
| 自定义单元格 | 日单元格外观与功能完全由你决定 | JTACDayCell.swift 与 JTACMonthViewProtocols.swift 的cellForItemAt |
| 自定义日历视图 | 日历整体外观与功能可自由设计 | JTACMonthView.swift 直接继承UICollectionView |
| 每周首日 | 可指定一周中任意一天作为第一天 | ConfigurationParameters.firstDayOfWeek,默认读取系统Calendar.firstWeekday |
| 横向或纵向模式 | 支持水平/垂直滚动 | JTACMonthView.swift 的scrollDirection |
| 月份头部 | 支持不同尺寸与样式的月份头 | JTACMonthViewProtocols.swift 的headerViewForDateRange |
| 按日期滚动 | 传入日期即可滚动到对应月份 | JTACInteractionMonthFunctions.swift 的scrollToDate(_:) |
提示:README 同时给出了演示动图链接和官方 Wiki/Tutorial 指引,本仓库内可直接运行的演示工程位于 SampleJTAppleCalendar,其中 TestViewController.swift、TestRangeSelectionViewController.swift 等示例可对照本文学习。
二、安装与工程结构
2.1 支持的分发方式
从仓库的打包配置可以看出该项目支持多种主流的 Swift 依赖管理方式:
- CocoaPods:JTAppleCalendar.podspec 声明版本
8.0.5、Swift 5、iOS/tvOS 最低部署目标11.0,源码文件为Sources/JTAppleCalendar/*.swift; - Swift Package Manager:Package.swift 使用
swift-tools-version:5.3,平台要求.iOS(.v12),提供名为JTAppleCalendar的 library target,并内置测试目标JTAppleCalendarTests; - Carthage:README 徽章中标注了 Carthage 兼容(
Carthage-compatible); - 仓库同时包含 JTAppleCalendar.xcodeproj 与 SampleJTAppleCalendar.xcodeproj 两个工程文件,以及单元测试 Tests/JTAppleCalendarTests 和 LinuxMain.swift。
2.2 源码模块划分
核心源码集中在 Sources/JTAppleCalendar,按职责拆分为多个文件:
- 视图主体:JTACMonthView.swift(日历视图,直接继承
UICollectionView)、JTACYearView.swift; - 协议:JTACMonthViewProtocols.swift(DataSource/Delegate);
- 数据模型:CalendarStructs.swift(
ConfigurationParameters、CellState、Month、DateSegmentInfo); - 枚举定义:CalendarEnums.swift;
- 布局与滚动:JTACMonthLayout.swift、JTACMonthLayoutHorizontalCalendar.swift、JTACMonthLayoutVerticalCalendar.swift;
- 交互与动作:JTACInteractionMonthFunctions.swift、JTACMonthActionFunctions.swift、JTACScrollViewDelegates.swift。
从源码结构看,JTACMonthView自己接管了UICollectionView的 dataSource 与 delegate(见 JTACMonthActionFunctions.swift 的super.dataSource = self),开发者只需遵循JTACMonthViewDataSource与JTACMonthViewDelegate两个协议即可驱动整个日历。
三、五分钟快速上手:最小可运行示例
3.1 在 Storyboard 或代码中创建日历
JTACMonthView是UICollectionView的子类(JTACMonthView.swift),因此你可以像拖一个 Collection View 一样把它放进 Storyboard,也可以纯代码创建。它自带init()便捷构造器,并自动管理自己的布局(JTACMonthView.swift):
import JTAppleCalendar let calendarView = JTACMonthView() calendarView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(calendarView)3.2 遵循两个协议
extension YourViewController: JTACMonthViewDataSource, JTACMonthViewDelegate { // 数据源:返回边界日期与配置参数 func configureCalendar(_ calendar: JTACMonthView) -> ConfigurationParameters { let formatter = DateFormatter() formatter.dateFormat = "yyyy MM dd" let startDate = formatter.date(from: "2017 01 01")! let endDate = formatter.date(from: "2030 02 01")! return ConfigurationParameters(startDate: startDate, endDate: endDate) } // 代理:配置每个日期单元格 func calendar(_ calendar: JTACMonthView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTACDayCell { let cell = calendar.dequeueReusableJTAppleCell( withReuseIdentifier: "cell", for: indexPath) as! CellView cell.dayLabel.text = cellState.text return cell } }以上代码取自仓库示例 TestViewController.swift 的完整写法:configureCalendar只传了起止日期,其余参数走默认值,日历即可正常渲染。
3.3 在 viewDidLoad 中接线
calendarView.calendarDataSource = self calendarView.calendarDelegate = self示例工程中CellView是自定义的JTACDayCell子类,通过 XIB 定义(见 CellView.swift 与 CellView.xib),这也引出了下一节要讲的自定义能力。
四、边界日期与 ConfigurationParameters 详解
README 的 "Boundary dates" 功能本质就是由ConfigurationParameters的startDate/endDate决定的。该结构体定义于 CalendarStructs.swift,完整初始化签名如下:
public init(startDate: Date, endDate: Date, numberOfRows: Int = 6, calendar: Calendar = Calendar.current, generateInDates: InDateCellGeneration = .forAllMonths, generateOutDates: OutDateCellGeneration = .tillEndOfGrid, firstDayOfWeek: DaysOfWeek? = nil, hasStrictBoundaries: Bool? = nil)各参数含义与默认值:
| 参数 | 默认值 | 作用 |
|---|---|---|
startDate/endDate | 无(必传) | 日历的起止边界;示例中2017 01 01至2030 02 01(TestViewController.swift) |
numberOfRows | 6 | 每个月分区的行数;只有1...6才生效,否则回退为 6(CalendarStructs.swift) |
calendar | Calendar.current | 用于计算的Calendar实例 |
generateInDates | .forAllMonths | 生成前置日期(上月补位)的模式:.forFirstMonthOnly/.forAllMonths/.off |
generateOutDates | .tillEndOfGrid | 生成后置日期(下月补位)的模式:.tillEndOfRow(补满一行)/.tillEndOfGrid(补满 6×7 网格)/.off |
firstDayOfWeek | 系统calendar.firstWeekday | 每周第一天,DaysOfWeek枚举(周日=1 ... 周六=7) |
hasStrictBoundaries | numberOfRows > 1 ? true : false | 当某月不满指定行数时,日期是否被限制在本月分区内(不跨月流窜);该值在注册了月份头部时会被忽略 |
4.1 底层如何计算"补位日期"
从源码可以确认这些参数直接参与月份网格的生成。CalendarStructs.swift 中的setupMonthInfoDataForStartAndEndDate(_:)会逐月计算:
- 根据
generateInDates决定是否计算本月前置日期数量numberOfPreDatesForThisMonth(numberOfInDatesForMonth依据firstDayOfWeek与当月 1 号的星期几算出,见 CalendarStructs.swift); - 根据
generateOutDates决定后置日期:.tillEndOfGrid固定按最多 6 行(maxNumberOfRowsPerMonth)生成,.tillEndOfRow则按实际行数补齐(CalendarStructs.swift); - 最终每个
Month结构体记录inDates、outDates、rows、numberOfDaysInMonthGrid等数据,供布局与 Cell 状态使用。
补充:
numberOfInDatesForMonth中firstDayOfWeek的换算表(周一=6、周二=5、周三=4、周四=10、周五=9、周六=8、周日=7)来自 CalendarStructs.swift,是理解"每周首日"配置如何影响网格布局的直接证据。
4.2 周视图与月视图切换
README 的 "Week/month mode" 对应numberOfRows = 1(单周)或2 / 3 / 6(多行月视图)。示例 TestRangeSelectionViewController.swift 展示了完整传参写法:
let parameter = ConfigurationParameters( startDate: startDate, endDate: endDate, numberOfRows: 6, generateInDates: .forAllMonths, generateOutDates: .tillEndOfGrid, firstDayOfWeek: .sunday)五、自定义日期 Cell:外观与交互的完全掌控
README 强调 "Custom cells"——日单元格的样式和功能完全由你决定,这是本库设计的核心理念。
5.1 自定义 Cell 的两种方式
- 代码方式:见 CodeCellView.swift;
- XIB 方式:见 CellView.swift + CellView.xib,示例 Cell 内部包含
dayLabel、monthLabel、selectedView三个视图元素。
Cell 必须继承JTACDayCell(源码位于 JTACDayCell.swift),并通过dequeueReusableJTAppleCell(withReuseIdentifier:for:)出队(JTACInteractionMonthFunctions.swift)。
5.2 CellState:驱动一切样式的数据源
CellState(CalendarStructs.swift)在cellForItemAt中随 Cell 一起传入,它携带:
isSelected:是否被选中;text:日期字符串(如 "1");dateBelongsTo:DateOwner枚举,区分thisMonth、previousMonthWithinBoundary、previousMonthOutsideBoundary、followingMonthWithinBoundary、followingMonthOutsideBoundary(CalendarEnums.swift)——示例用它把当月日期染黑、把补位日期染灰(TestViewController.swift);date、day(DaysOfWeek)、row()、column()、dateSection();selectedPosition():区间选择时的位置(.left/.middle/.right/.full/.none,CalendarEnums.swift);selectionType:.programatic或.userInitiated(CalendarEnums.swift)。
示例工程中用selectedPosition()分别给区间首、中、尾涂上不同颜色:
switch cellState.selectedPosition() { case .full: view.backgroundColor = .green case .left: view.backgroundColor = .yellow case .right: view.backgroundColor = .red case .middle: view.backgroundColor = .blue case .none: view.backgroundColor = nil }(摘自 TestViewController.swift)。
5.3 头部 View 的自定义
README 提到 "Ability to add month headers in varying sizes/styles"。月份头部继承JTACMonthReusableView,通过headerViewForDateRange代理方法返回。仓库给出了代码绘制头部的示例 CodePinkSectionHeaderView.swift,以及 XIB 版本 PinkSectionHeaderView.xib。头部尺寸通过calendarSizeForMonths(_:)返回的MonthSize控制——MonthSize支持defaultSize统一尺寸,也可按月份(months)或具体日期(dates)单独指定(CalendarStructs.swift)。
六、区间选择(Range Selection)实战
README 的 "Range selection - select dates in a range. The design is entirely up to you" 在本库中有两层含义:库负责"范围",设计交给你的 Cell。
6.1 开启区间选择
calendarView.allowsMultipleSelection = true calendarView.allowsRangedSelection = true // 见 JTACMonthView.swiftallowsRangedSelection定义于 JTACMonthView.swift,注释明确说明:启用后每次点击日期单元格,左右两侧的单元格都会快速刷新,用于计算区间内每个 Cell 的选中位置。rangeSelectionMode则提供.segmented(跨月分段视觉断开)与.continuous(连续)两种模式(JTACMonthView.swift 与 CalendarEnums.swift)。
6.2 程序化选择区间
示例 TestViewController.swift 展示了直接选中一整段日期:
let date = formatter.date(from: "2017 01 01")! let date2 = formatter.date(from: "2017 12 25")! calendarView.selectDates(from: date, to: date2, triggerSelectionDelegate: true)底层实现selectDates(from:to:triggerSelectionDelegate:keepSelectionIfMultiSelectionAllowed:)会调用generateDateRange(from:to:)生成连续的Date数组后再批量选中(JTACInteractionMonthFunctions.swift 与 JTACInteractionMonthFunctions.swift)。另外还有selectDates(_:triggerSelectionDelegate:keepSelectionIfMultiSelectionAllowed:)可传入任意日期数组,deselect(dates:...)/deselectAllDates(triggerSelectionDelegate:)用于反选(JTACInteractionMonthFunctions.swift)。
6.3 区间样式绘制范例
仓库的 TestRangeSelectionViewController.swift 给出了经典的"胶囊形区间"绘制:根据selectedPosition()设置selectedView的cornerRadius与maskedCorners,让区间首尾圆角、中间平直,视觉上连成一条完整的选择带。
七、滚动:横向/纵向、分页与按日期滚动
7.1 滚动方向与滚动模式
scrollDirection:UICollectionView.ScrollDirection的.horizontal或.vertical(JTACMonthView.swift);scrollingMode:ScrollingMode枚举(CalendarEnums.swift),包括:.stopAtEachCalendarFrame(每帧停靠,默认,等价于分页)、.stopAtEachSection(每分区停靠)、.stopAtEach(customInterval:)(自定义间隔);.nonStopToSection(withResistance:)、.nonStopToCell(withResistance:)、.nonStopTo(customInterval:withResistance:)(带阻力的连续滚动);.none(自然减速停止)。
设置scrollingMode时,内部会同步调整isPagingEnabled与decelerationRate(JTACMonthView.swift)。
7.2 按日期滚动到任意月份
README 的 "Ability to scroll to any month by simply using the date" 由scrollToDate实现:
public func scrollToDate(_ date: Date, triggerScrollToDateDelegate: Bool = true, animateScroll: Bool = true, preferredScrollPosition: UICollectionView.ScrollPosition? = nil, extraAddedOffset: CGFloat = 0, completionHandler: (() -> Void)? = nil)(定义见 JTACInteractionMonthFunctions.swift)。传入任意Date,日历会先通过pathsFromDates(_:)找到对应 IndexPath,再按滚动模式计算目标偏移量并滚动过去;若日期超出边界则忽略(JTACMonthView.swift 的requestedContentOffset逻辑)。
7.3 分段滚动与可见日期回调
scrollToSegment(_:triggerScrollToDateDelegate:animateScroll:extraAddedOffset:completionHandler:):按.next/.previous/.start/.end(SegmentDestination,CalendarEnums.swift)滚动到相邻或首尾分段(JTACInteractionMonthFunctions.swift);- 滚动期间会回调
willScrollToDateSegmentWith与didScrollToDateSegmentWith,参数是DateSegmentInfo——它把当前可见日期分为indates(前置补位)、monthDates(本月)、outdates(后置补位)三组(CalendarStructs.swift)。示例用visibleDates().monthDates.first更新顶部的月份标题(TestViewController.swift); - 还可在任意时刻调用
visibleDates()同步查询当前可见日期,或传入 completionHandler 异步获取(JTACInteractionMonthFunctions.swift)。
7.4 高度/方向变化后的重载
reloadData(withAnchor:completionHandler:)支持传入锚定日期,在刷新完成后自动滚动到该日期(JTACInteractionMonthFunctions.swift);viewWillTransition(to:with:anchorDate:)则用于屏幕旋转时保持视觉焦点(JTACInteractionMonthFunctions.swift)。示例工程在viewWillTransition中调用它以适配横竖屏切换(TestViewController.swift)。
八、实用工具 API 速查
除上述核心能力外,仓库还提供了一批高价值工具方法(均位于 JTACInteractionMonthFunctions.swift):
| API | 用途 |
|---|---|
cellStatus(for date: Date) | 查询某个日期对应 Cell 的CellState(未加载或加载中时返回 nil) |
cellStatus(for date: Date, completionHandler:) | 异步版本,布局未就绪时会自动延迟执行 |
cellStatus(at point: CGPoint) | 按屏幕坐标查询 Cell 状态 |
cellStatusForDate(at row:column:) | 按行列查询当前分区内的 Cell 状态 |
monthStatus(for date: Date) | 返回日期所属的Month结构体 |
generateDateRange(from:to:) | 生成起止日期之间的连续日期数组 |
reloadDates(_:) | 仅重载指定日期对应的 Cell(及其跨月镜像 Cell) |
deselect(dates:triggerSelectionDelegate:keepDeselectionIfMultiSelectionAllowed:) | 反选指定日期 |
另外两个实用开关值得一提:
allowsDateCellStretching(默认true):当月不足 6 行时,允许 Cell 拉伸填满整行宽度(JTACMonthView.swift);semanticContentAttribute:支持 RTL(从右到左)阅读方向,切换时视图会做镜像翻转(JTACMonthView.swift)。
九、测试与质量保障
仓库在 Tests/JTAppleCalendarTests 中提供了单元测试(JTAppleCalendarTests.swift、XCTestManifests.swift),并在 LinuxMain.swift 中注册了 Linux 平台的测试入口;Package.swift 的 testTarget 声明了JTAppleCalendarTests依赖主 target。示例工程还包含 UI 测试 SampleJTAppleCalendarUITests.swift。如果你在集成后需要验证日期网格、选择与滚动的正确性,可以参考这些测试的组织方式。
十、总结:从 README 到可运行日历
回顾 README 的核心承诺——"You want it, you build it",JTAppleCalendar 的设计哲学可以归纳为三点:
- 数据边界由配置驱动:
ConfigurationParameters一个结构体集中管理边界日期、行数、每周首日、补位日期生成策略,示例代码四五行即可配置完成; - 外观全部下沉到 Cell:库只负责把"哪个 Cell 对应哪个日期、处于什么选中位置"通过
CellState告诉你的自定义JTACDayCell,渲染逻辑完全由你掌控; - 交互能力开箱即用:区间选择、按日期滚动、分段滚动、可见日期回调等 API 都已封装完毕,与原生
UICollectionView的手感无缝衔接。
如果你希望进一步探索,建议依次阅读 SampleJTAppleCalendar/Example Calendars 下的多个示例控制器(覆盖普通日历、区间选择、波斯历、年份视图、横竖屏切换等场景),再对照 Sources/JTAppleCalendar 中的源码理解每个开关的底层行为。
- 移动开发
- UI组件
【免费下载链接】JTAppleCalendar
The Unofficial Apple iOS Swift Calendar View. Swift calendar Library. iOS calendar Control. 100% Customizable
相关推荐
Hyper Fetch性能优化指南:减少网络请求的7个高级策略
Hyper Fetch性能优化指南:减少网络请求的7个高级策略 Hyper Fetch是一个强大的实时数据交换框架,通过优化网络请求流程显著提升应用性能。本文将
终极iOS日历定制指南:JTAppleCalendar让你的应用日历焕然一新
终极iOS日历定制指南:JTAppleCalendar让你的应用日历焕然一新 还记得那个让你头疼的iOS日历开发经历吗?当你需要为应用添加一个简单的日期选择器时
移动开发UI组件CVCalendar 开源项目教程:打造 iOS 自定义日历的终极指南
CVCalendar 开源项目教程:打造 iOS 自定义日历的终极指南 还在为 iOS 应用中的日历功能而烦恼吗?想要一个既美观又功能强大的自定义日历组件?CV
移动开发UI库/组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考