Skip to content

Instantly share code, notes, and snippets.

Last active May 8, 2024 13:30
Show Gist options
  • Save mecid/f8859ea4bdbd02cf5d440d58e936faec to your computer and use it in GitHub Desktop.
Save mecid/f8859ea4bdbd02cf5d440d58e936faec to your computer and use it in GitHub Desktop.
SwiftUI Calendar view using LazyVGrid
import SwiftUI
extension Calendar {
func generateDates(
inside interval: DateInterval,
matching components: DateComponents
) -> [Date] {
var dates: [Date] = []
startingAfter: interval.start,
matching: components,
matchingPolicy: .nextTime
) { date, _, stop in
if let date = date {
if date < interval.end {
} else {
stop = true
return dates
extension DateFormatter {
static let monthAndYear: DateFormatter = {
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("MMMM yyyy")
return formatter
struct EquatableCalendarView<DateView: View, Value: Equatable>: View, Equatable {
static func == (
lhs: EquatableCalendarView<DateView, Value>,
rhs: EquatableCalendarView<DateView, Value>
) -> Bool {
lhs.interval == rhs.interval && lhs.value == rhs.value && lhs.showHeaders == rhs.showHeaders
let interval: DateInterval
let value: Value
let showHeaders: Bool
let onHeaderAppear: (Date) -> Void
let content: (Date) -> DateView
interval: DateInterval,
value: Value,
showHeaders: Bool = true,
onHeaderAppear: @escaping (Date) -> Void = { _ in },
@ViewBuilder content: @escaping (Date) -> DateView
) {
self.interval = interval
self.value = value
self.showHeaders = showHeaders
self.onHeaderAppear = onHeaderAppear
self.content = content
var body: some View {
interval: interval,
showHeaders: showHeaders,
onHeaderAppear: onHeaderAppear
) { date in
struct CalendarView<DateView>: View where DateView: View {
let interval: DateInterval
let showHeaders: Bool
let onHeaderAppear: (Date) -> Void
let content: (Date) -> DateView
@Environment(\.sizeCategory) private var contentSize
@Environment(\.calendar) private var calendar
@State private var months: [Date] = []
@State private var days: [Date: [Date]] = [:]
private var columns: [GridItem] {
let spacing: CGFloat = contentSize.isAccessibilityCategory ? 2 : 8
return Array(repeating: GridItem(spacing: spacing), count: 7)
var body: some View {
LazyVGrid(columns: columns) {
ForEach(months, id: \.self) { month in
Section(header: header(for: month)) {
ForEach(days[month, default: []], id: \.self) { date in
if calendar.isDate(date, equalTo: month, toGranularity: .month) {
} else {
.onAppear {
months = calendar.generateDates(
inside: interval,
matching: DateComponents(day: 1, hour: 0, minute: 0, second: 0)
days = months.reduce(into: [:]) { current, month in
let monthInterval = calendar.dateInterval(of: .month, for: month),
let monthFirstWeek = calendar.dateInterval(of: .weekOfMonth, for: monthInterval.start),
let monthLastWeek = calendar.dateInterval(of: .weekOfMonth, for: monthInterval.end)
else { return }
current[month] = calendar.generateDates(
inside: DateInterval(start: monthFirstWeek.start, end: monthLastWeek.end),
matching: DateComponents(hour: 0, minute: 0, second: 0)
private func header(for month: Date) -> some View {
Group {
if showHeaders {
Text(DateFormatter.monthAndYear.string(from: month))
.onAppear { onHeaderAppear(month) }
Copy link

@basememara for your month-traversing implementation is there a way to retain the selected date when switching to another month? as it stands it updates selected date to the same day in the month that is moved to.

Copy link

Hi you guys.
Do you know how I can draw a background like image below?

Calendar—Left Frame

Copy link

vinhnx commented Oct 21, 2023

thank you @mecid for this gist,

I even built an Calendar app using your component

Copy link

iletai commented Nov 23, 2023

Thank you for sharing. It's changed my mindset about my calendar before. I had to think about the calendar, it's faster if you use the library
But with swiftui, it's more flexible. It doesn't take as much time as I thought

  • I personally think it should become a repository for more contributions, updates, and migration. Wait for it.

Copy link

iletai commented Nov 30, 2023

From the idea of this gist. I had created an example for a Calendar that can be customized more. Thank you.
Hope this helps someone.

Copy link

@mecid, @filimo, @funkenstrahlen How can I enable horizontal scrolling for this custom table calendar? Currently, when I scroll horizontally, it navigates to the next or previous month. I've been searching for a solution so far. If anyone knows the answer, please share it with me. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment