☰
JTAppleCalendar 使用指南:打造 100% 可自定义的 iOS Swift 日历视图
2026/9/27 21:29:21 网站建设 项目流程
  • 移动开发
  • UI组件

【免费下载链接】JTAppleCalendar

The Unofficial Apple iOS Swift Calendar View. Swift calendar Library. iOS calendar Control. 100% Customizable

项目地址:https://gitcode.com/gh_mirrors/jt/JTAppleCalendar
点击查看免费下载

导读

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)
numberOfRows6每个月分区的行数;只有1...6才生效,否则回退为 6(CalendarStructs.swift)
calendarCalendar.current用于计算的Calendar实例
generateInDates.forAllMonths生成前置日期(上月补位)的模式:.forFirstMonthOnly/.forAllMonths/.off
generateOutDates.tillEndOfGrid生成后置日期(下月补位)的模式:.tillEndOfRow(补满一行)/.tillEndOfGrid(补满 6×7 网格)/.off
firstDayOfWeek系统calendar.firstWeekday每周第一天,DaysOfWeek枚举(周日=1 ... 周六=7)
hasStrictBoundariesnumberOfRows > 1 ? true : false当某月不满指定行数时,日期是否被限制在本月分区内(不跨月流窜);该值在注册了月份头部时会被忽略

4.1 底层如何计算"补位日期"

从源码可以确认这些参数直接参与月份网格的生成。CalendarStructs.swift 中的setupMonthInfoDataForStartAndEndDate(_:)会逐月计算:

  1. 根据generateInDates决定是否计算本月前置日期数量numberOfPreDatesForThisMonth(numberOfInDatesForMonth依据firstDayOfWeek与当月 1 号的星期几算出,见 CalendarStructs.swift);
  2. 根据generateOutDates决定后置日期:.tillEndOfGrid固定按最多 6 行(maxNumberOfRowsPerMonth)生成,.tillEndOfRow则按实际行数补齐(CalendarStructs.swift);
  3. 最终每个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.swift

allowsRangedSelection定义于 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 的设计哲学可以归纳为三点:

  1. 数据边界由配置驱动:ConfigurationParameters一个结构体集中管理边界日期、行数、每周首日、补位日期生成策略,示例代码四五行即可配置完成;
  2. 外观全部下沉到 Cell:库只负责把"哪个 Cell 对应哪个日期、处于什么选中位置"通过CellState告诉你的自定义JTACDayCell,渲染逻辑完全由你掌控;
  3. 交互能力开箱即用:区间选择、按日期滚动、分段滚动、可见日期回调等 API 都已封装完毕,与原生UICollectionView的手感无缝衔接。

如果你希望进一步探索,建议依次阅读 SampleJTAppleCalendar/Example Calendars 下的多个示例控制器(覆盖普通日历、区间选择、波斯历、年份视图、横竖屏切换等场景),再对照 Sources/JTAppleCalendar 中的源码理解每个开关的底层行为。

  • 移动开发
  • UI组件

【免费下载链接】JTAppleCalendar

The Unofficial Apple iOS Swift Calendar View. Swift calendar Library. iOS calendar Control. 100% Customizable

项目地址:https://gitcode.com/gh_mirrors/jt/JTAppleCalendar
点击查看免费下载

相关推荐

上一篇:class-transformer CommonJS与ESM兼容性处理:跨模块使用
下一篇:Eve多语言支持终极指南:如何实现国际化与本地化最佳实践

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询