Skip to content

Instantly share code, notes, and snippets.

@jz709u
Forked from mecid/Calendar.swift
Last active March 2, 2023 03:41
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jz709u/ed97507a8655ce5b23e205b0feea80bb to your computer and use it in GitHub Desktop.
Save jz709u/ed97507a8655ce5b23e205b0feea80bb to your computer and use it in GitHub Desktop.
SwiftUI Calendar view using LazyVGrid
import SwiftUI
fileprivate extension DateFormatter {
static var month: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM"
return formatter
}
static var monthAndYear: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM yyyy"
return formatter
}
}
fileprivate extension Calendar {
func generateDates(
inside interval: DateInterval,
matching components: DateComponents
) -> [Date] {
var dates: [Date] = []
dates.append(interval.start)
enumerateDates(
startingAfter: interval.start,
matching: components,
matchingPolicy: .nextTime
) { date, _, stop in
if let date = date {
if date < interval.end {
dates.append(date)
} else {
stop = true
}
}
}
return dates
}
}
struct CalendarView<DateView>: View where DateView: View {
@Environment(\.calendar) var calendar
let interval: DateInterval
let showHeaders: Bool
let content: (Date) -> DateView
init(
interval: DateInterval,
showHeaders: Bool = true,
@ViewBuilder content: @escaping (Date) -> DateView
) {
self.interval = interval
self.showHeaders = showHeaders
self.content = content
months = calendar.generateDates(
inside: interval,
matching: DateComponents(day: 1, hour: 0, minute: 0, second: 0)
)
for month in months {
if showHeaders {
monthHeaders += [header(for: month)]
}
monthToDays[month] = days(for: month)
}
}
var body: some View {
LazyVGrid(columns: Array(repeating: GridItem(), count: 7)) {
ForEach(Array(months.enumerated()), id: \.offset) { (index,month) in
Section(header: headerView(for: monthHeaders[index])) {
if let days = monthToDays[month] {
ForEach(days, id: \.self) { date in
if calendar.isDate(date, equalTo: month, toGranularity: .month) {
content(date).id(date)
} else {
content(date).hidden()
}
}
}
}
}
}
}
private var months = [Date]()
private var monthHeaders = [String]()
private var monthToDays = [Date: [Date]]()
// Important: dont add padding or any frame modifications to the headerView or you will get performance issues
private func headerView(for month:String) -> some View {
Text(month)
.font(.title)
}
private func header(for month: Date) -> String {
let component = calendar.component(.month, from: month)
let formatter = component == 1 ? DateFormatter.monthAndYear : .month
return formatter.string(from: month)
}
private func days(for month: Date) -> [Date] {
guard
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 [] }
return calendar.generateDates(
inside: DateInterval(start: monthFirstWeek.start, end: monthLastWeek.end),
matching: DateComponents(hour: 0, minute: 0, second: 0)
)
}
}
struct CalendarView_Previews: PreviewProvider {
static var previews: some View {
CalendarView(interval: .init()) { _ in
Text("30")
.padding(8)
.background(Color.blue)
.cornerRadius(8)
}
}
}
@chrisriner
Copy link

Thanks for getting back to me but I figured out my issue.

@mecid
Copy link

mecid commented Jun 24, 2021

Hey, thanks for the improved version. SwiftUI will recreate the calendar view on every change, that's why it is better to generate dates in onAppear and store them in @State property. It should improve performance drastically.

@obskera
Copy link

obskera commented Jan 17, 2022

As a complete newbie regarding Date and Calendar related stuff, how do you use this view? When I try to use "CalendarView()" it just starts saying "Generic parameter 'DateView' could not be inferred" and I'm not exactly sure what I am supposed to be doing to actually USE the view once it's written up :/

@mecid
Copy link

mecid commented Jan 18, 2022

@obskera take a look at CalendarView_Previews struct, it shows how to use calendar.

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