3

Im trying to get the current month when the calendar is being scrolled. My goal is to have the header with the current month, then when the view is scrolled down to a new month the header updates with the corresponding month. I tried using a LazyVStack and in the onAppear method set the current month, but using the lazyVStack is never consistent and is always off by a few months. The code down below is optimized for an iPad calendar month view.

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)
    }
}
Carterman
  • 219
  • 1
  • 7

0 Answers0