Created
October 2, 2020 14:50
-
-
Save huynguyencong/f0423b1068d0edeef948a5b6b1c66967 to your computer and use it in GitHub Desktop.
A simple calendar view, some improvement from the original code: https://gist.github.com/mecid/f8859ea4bdbd02cf5d440d58e936faec/9169b0293f709bb1f560de2ca8184ea903fd5116
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CalendarView.swift | |
// iWidget | |
// | |
// Created by Huy Nguyen on 10/2/20. | |
// | |
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 WeekView<DateView>: View where DateView: View { | |
let week: Date | |
let calendar: Calendar | |
let highlightDate: Date? | |
let content: (Date, Bool) -> DateView | |
init(week: Date, calendar: Calendar, highlightDate: Date?, @ViewBuilder content: @escaping (Date, Bool) -> DateView) { | |
self.week = week | |
self.calendar = calendar | |
self.highlightDate = highlightDate | |
self.content = content | |
} | |
private var days: [Date] { | |
guard | |
let weekInterval = calendar.dateInterval(of: .weekOfYear, for: week) | |
else { return [] } | |
return calendar.generateDates( | |
inside: weekInterval, | |
matching: DateComponents(hour: 0, minute: 0, second: 0) | |
) | |
} | |
var body: some View { | |
HStack(spacing: 0) { | |
ForEach(days, id: \.self) { date in | |
if calendar.isDate(self.week, equalTo: date, toGranularity: .month) { | |
content(date, isHighlightDate(date)) | |
} else { | |
content(date, false) | |
.hidden() | |
} | |
} | |
} | |
} | |
func isHighlightDate(_ date: Date) -> Bool { | |
guard let highlightDate = highlightDate else { return false } | |
return calendar.isDate(date, inSameDayAs: highlightDate) | |
} | |
} | |
struct MonthView<DateView: View, WeekDate: View>: View { | |
let month: Date | |
let calendar: Calendar | |
let weekDateView: (String) -> WeekDate | |
let content: (Date, Bool) -> DateView | |
let highlightDate: Date? | |
init(month: Date, calendar: Calendar, highlightDate: Date?, @ViewBuilder weekDateView: @escaping (String) -> WeekDate, @ViewBuilder content: @escaping (Date, Bool) -> DateView) { | |
self.month = month | |
self.calendar = calendar | |
self.highlightDate = highlightDate | |
self.weekDateView = weekDateView | |
self.content = content | |
} | |
init(month: Int, year: Int, calendar: Calendar, highlightDate: Date? = nil, @ViewBuilder weekDateView: @escaping (String) -> WeekDate, @ViewBuilder content: @escaping (Date, Bool) -> DateView) { | |
var dateComponents = DateComponents() | |
dateComponents.setValue(month, for: .month) | |
dateComponents.setValue(year, for: .year) | |
let month = Calendar.current.date(from: dateComponents) ?? Date() | |
self.init(month: month, calendar: calendar, highlightDate: highlightDate, weekDateView: weekDateView, content: content) | |
} | |
private var weeks: [Date] { | |
guard | |
let monthInterval = calendar.dateInterval(of: .month, for: month) | |
else { return [] } | |
return calendar.generateDates( | |
inside: monthInterval, | |
matching: DateComponents(hour: 0, minute: 0, second: 0, weekday: calendar.firstWeekday) | |
) | |
} | |
private var firstWeekDays: [Date] { | |
guard let firstWeek = weeks.first, | |
let weekInterval = calendar.dateInterval(of: .weekOfYear, for: firstWeek) | |
else { return [] } | |
return calendar.generateDates( | |
inside: weekInterval, | |
matching: DateComponents(hour: 0, minute: 0, second: 0) | |
) | |
} | |
private let dateFormater = { () -> DateFormatter in | |
let dateFormatter = DateFormatter() | |
dateFormatter.dateFormat = "E" | |
return dateFormatter | |
}() | |
var body: some View { | |
VStack(spacing: 0) { | |
HStack(spacing: 0) { | |
ForEach(firstWeekDays, id: \.self) { date in | |
weekDateView(dateFormater.string(from: date)) | |
} | |
} | |
ForEach(weeks, id: \.self) { week in | |
WeekView(week: week, calendar: calendar, highlightDate: highlightDate, content: self.content) | |
} | |
} | |
} | |
} | |
struct CalendarView<DateView: View, WeekDate: View>: View { | |
let interval: DateInterval | |
let calendar: Calendar | |
let highlightDate: Date? | |
let weekDateView: (String) -> WeekDate | |
let content: (Date, Bool) -> DateView | |
init(interval: DateInterval, calendar: Calendar, highlightDate: Date?, @ViewBuilder weekDateView: @escaping (String) -> WeekDate, @ViewBuilder content: @escaping (Date, Bool) -> DateView) { | |
self.interval = interval | |
self.calendar = calendar | |
self.highlightDate = highlightDate | |
self.weekDateView = weekDateView | |
self.content = content | |
} | |
private var months: [Date] { | |
calendar.generateDates( | |
inside: interval, | |
matching: DateComponents(day: 1, hour: 0, minute: 0, second: 0) | |
) | |
} | |
var body: some View { | |
ScrollView(.vertical, showsIndicators: false) { | |
VStack { | |
ForEach(months, id: \.self) { month in | |
MonthView(month: month, calendar: calendar, highlightDate: highlightDate, weekDateView: weekDateView, content: self.content) | |
} | |
} | |
} | |
} | |
} | |
// Usage: | |
struct ContentView: View { | |
let calendar: Calendar = { () -> Calendar in | |
var calendar = Calendar.current | |
calendar.firstWeekday = 0 | |
return calendar | |
}() | |
var body: some View { | |
NavigationView { | |
MonthView(month: 10, year: 2020, calendar: Calendar.current, highlightDate: Date(), weekDateView: { text in | |
Text(text) | |
.font(.system(size: 13)) | |
.frame(width: 30, height: 30) | |
.padding(.horizontal, 3) | |
}) { date, isHighlight in | |
Text(String(Calendar.current.component(.day, from: date))) | |
.foregroundColor(isHighlight ? Color.white : Color.orange) | |
.frame(width: 30, height: 30) | |
.background(isHighlight ? Color.orange : Color.clear) | |
.clipShape(Circle()) | |
.padding(3) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment