Building a Custom Calendar Component in SwiftUI
When I started building SwiftUICalendarView↗, my goal was simple: create a calendar component that is 100% SwiftUI with no UIKit fallbacks. Here is what I learned.
Why Pure SwiftUI?
UIKit calendar components work, but they break the SwiftUI mental model. With a pure SwiftUI approach, you get:
- Declarative syntax — configure with modifiers, not delegates
- Cross-platform — iOS, macOS, watchOS, tvOS from one codebase
- Preview support — instant feedback in Xcode Previews
- Animation — built-in SwiftUI transitions
Core Architecture
The calendar is built around three main types:
1public struct CalendarView<DateView: View>: View {
2 @Binding var selectedDate: Date?
3 let calendar: Calendar
4 let content: (Date) -> DateView
5
6 public var body: some View {
7 LazyVGrid(columns: Array(repeating: GridItem(), count: 7)) {
8 ForEach(daysInMonth, id: \.self) { date in
9 content(date)
10 .onTapGesture { selectedDate = date }
11 }
12 }
13 }
14}The key insight is using a generic DateView — this lets users fully customize how each date cell looks.
Custom Date Views
Users can provide any SwiftUI view for dates:
1CalendarView(selectedDate: $selectedDate) { date in
2 VStack {
3 Text("\(Calendar.current.component(.day, from: date))")
4 .font(.headline)
5
6 if hasEvent(on: date) {
7 Circle()
8 .fill(.red)
9 .frame(width: 6, height: 6)
10 }
11 }
12 .frame(maxWidth: .infinity, minHeight: 40)
13 .background(isSelected(date) ? Color.blue.opacity(0.3) : .clear)
14 .cornerRadius(8)
15}Month Navigation
Smooth month transitions using SwiftUI gestures:
1.gesture(
2 DragGesture()
3 .onEnded { value in
4 withAnimation(.easeInOut) {
5 if value.translation.width < -50 {
6 currentMonth = calendar.date(byAdding: .month, value: 1, to: currentMonth)!
7 } else if value.translation.width > 50 {
8 currentMonth = calendar.date(byAdding: .month, value: -1, to: currentMonth)!
9 }
10 }
11 }
12)Selection Modes
The library supports multiple selection modes:
- Single: Tap to select one date
- Range: Select a start and end date
- Multiple: Select multiple individual dates
1CalendarView(
2 selectedDate: $selectedDate,
3 selectionMode: .range
4) { date in
5 // Your custom date view
6}Swift Package Manager Distribution
Publishing as an SPM package:
1// Package.swift
2let package = Package(
3 name: "SwiftUICalendarView",
4 platforms: [.iOS(.v15), .macOS(.v12), .watchOS(.v8)],
5 products: [
6 .library(name: "SwiftUICalendarView", targets: ["SwiftUICalendarView"]),
7 ],
8 targets: [
9 .target(name: "SwiftUICalendarView"),
10 .testTarget(name: "SwiftUICalendarViewTests", dependencies: ["SwiftUICalendarView"]),
11 ]
12)Lessons Learned
- LazyVGrid is your friend — perfect for calendar grids
- Generic views give flexibility — users customize without subclassing
- Calendar API is complex — handle locale, time zones, and edge cases carefully
- Test with different calendars — not just Gregorian
The project has grown to 125+ stars on GitHub, and I continue to improve it based on community feedback. Check it out at github.com/iletai/SwiftUICalendarView↗!