カレンダーがスクロールされている現在の月を取得しようとしています。私の目標は、現在の月のヘッダーを表示することです。次に、ビューを新しい月までスクロールすると、ヘッダーが対応する月に更新されます。LazyVStack を使用してみましたが、onAppear メソッドで現在の月を設定しましたが、lazyVStack の使用は一貫性がなく、常に数か月ずれています。以下のコードは、iPad カレンダーの月表示用に最適化されています。
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 {
@Environment(\.calendar) var calendar
let week: Date
let content: (Date) -> DateView
init(week: Date, @ViewBuilder content: @escaping (Date) -> DateView) {
self.week = week
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)
)
}
func dateInWeekened(date: Date) -> Bool {
var bool = false
if Calendar.current.isDateInWeekend(date) {
bool = true
}
return bool
}
var body: some View {
HStack(spacing: 0.0) {
ForEach(days, id: \.self) { date in
HStack(alignment: .top, spacing: 0.0) {
Divider()
VStack(alignment: .trailing, spacing: 0.0){
if self.calendar.isDate(self.week, equalTo: date, toGranularity: .month) {
HStack{
Spacer()
self.content(date)
.foregroundColor(dateInWeekened(date: date) == true ? .secondary : .primary)
.padding(8)
}
} else {
HStack{
Spacer()
self.content(date)
.hidden()
}
}
Spacer()
}
}
.frame(height: UIScreen.screenHeight / 7)
.background(dateInWeekened(date: date) == true ? Color("CalendarWeekenedColor") : Color(.systemBackground))
.edgesIgnoringSafeArea(.all)
}
}
}
}
struct MonthView<DateView>: View where DateView: View {
@Environment(\.calendar) var calendar
let month: Date
let showHeader: Bool
let content: (Date) -> DateView
init(
month: Date,
showHeader: Bool = true,
@ViewBuilder content: @escaping (Date) -> DateView
) {
self.month = month
self.content = content
self.showHeader = showHeader
}
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 header: some View {
let component = calendar.component(.month, from: month)
let formatter = component == 1 ? DateFormatter.monthAndYear : .month
return
HStack{Text(formatter.string(from: month))
.font(.title)
.padding()
Spacer()
}.padding(.leading)
}
var body: some View {
VStack(spacing: 0.0) {
if showHeader {
header
}
else {
ForEach(weeks, id: \.self) { week in
WeekView(week: week, content: self.content)
.edgesIgnoringSafeArea(.all)
Divider()
.edgesIgnoringSafeArea(.all)
}
}
}
}
}
struct CalendarView<DateView>: View where DateView: View {
@Environment(\.calendar) var calendar
let interval: DateInterval
let content: (Date) -> DateView
@Binding var curentDate: Date
@State var position = 0
init(interval: DateInterval, @ViewBuilder content: @escaping (Date) -> DateView, currentDate: Binding<Date>) {
self.interval = interval
self.content = content
self._curentDate = currentDate
}
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) {
LazyVStack(spacing: 0.0){
ForEach(months, id: \.self) { month in
MonthView(month: month, showHeader: false, content: self.content)
.edgesIgnoringSafeArea(.all)
.frame(maxHeight: .infinity)
.onAppear {
let component = calendar.component(.month, from: month)
let formatter = component == 1 ? DateFormatter.monthAndYear : .month
print("Current Month is \(formatter.string(from: month))")
}
}
}
}
}
}
struct CalendarMonthView: View {
@Environment(\.calendar) var calendar
@Binding var currentDate: Date
private var year: DateInterval {
calendar.dateInterval(of: .year, for: Date())!
}
var body: some View {
CalendarView(interval: year, content: { date in
Text("30")
.hidden()
.overlay(
Text(String(self.calendar.component(.day, from: date)))
)
}, currentDate: $currentDate)
.background(Color("NavigationBarColor"))
.edgesIgnoringSafeArea(.all)
}
}