A pure-SwiftUI calendar component that collapses smoothly between month view and week view via drag gesture, with horizontal month paging and per-date badge support.
- Month ↔ Week collapse — drag-driven, spring-animated transition between full month grid and single-week strip
- Horizontal month paging — swipe left/right to navigate months with crossfade transition
- Badge counts — per-date badge overlay (top-right corner), driven by a
(Date) -> Intclosure - Rubber-band overscroll — elastic feel when dragging past collapse bounds
- Scroll-linked gesture — collapse only activates when the content scroll view is at the top
- Zero dependencies — pure SwiftUI, no external packages
- iOS 18.0+
- Swift 6.0+
- Xcode 16.0+
Why iOS 18? The month paging strip in
CompactCalendarViewis built on the SwiftUI scroll enhancements introduced in iOS 18:scrollPosition(id:),scrollTargetBehavior(.paging),scrollTransition(.interactive, ...),onScrollGeometryChange, andcontainerRelativeFrame. These APIs are not available on iOS 17.
Add the package in Xcode via File → Add Package Dependencies, or in Package.swift:
dependencies: [
.package(url: "https://github.com/mark-x64/MBExpandableCalendar", from: "1.0.0")
]Then add the target:
.target(
name: "YourApp",
dependencies: [
.product(name: "MBExpandableCalendar", package: "MBExpandableCalendar")
]
)The full-featured container: calendar header on top, your scrollable content below, with built-in collapse gesture coordination.
import MBExpandableCalendar
struct CalendarScreen: View {
@State private var selectedDate = Date()
var body: some View {
ExpandableCalendarContainer(
selectedDate: $selectedDate,
badgeCount: { date in
Calendar.current.isDateInToday(date) ? 3 : 0
}
) { selectedDate in
VStack(alignment: .leading, spacing: 12) {
Text("Selected: \(selectedDate, format: .dateTime.month().day())")
}
.padding()
}
}
}The container owns the outer scroll view and collapse coordination. Provide normal SwiftUI content for the selected date.
Use the calendar grid on its own when you need custom gesture handling or a non-standard layout:
import MBExpandableCalendar
CompactCalendarView(
selectedDate: $date,
badgeCount: { _ in 0 },
collapse: collapseValue, // 0 = month, 1 = week
isDraggingVertically: isDragging,
suppressTap: suppress
)| Parameter | Type | Description |
|---|---|---|
selectedDate |
Binding<Date> |
Currently selected date |
badgeCount |
(Date) -> Int |
Badge count for each date |
content |
(Date) -> Content |
Content below the calendar; receives the selected date |
| Parameter | Type | Default | Description |
|---|---|---|---|
selectedDate |
Binding<Date> |
— | Currently selected date |
badgeCount |
(Date) -> Int |
— | Badge count for each date |
overscaleAnchor |
UnitPoint |
.center |
Anchor for rubber-band scale effect |
collapse |
CGFloat |
0 |
Collapse progress: 0 = month, 1 = week |
isDraggingVertically |
Bool |
false |
Disables horizontal paging during vertical drag |
suppressTap |
Bool |
false |
Prevents date taps during a drag gesture |
MIT








