Reputation: 21
Explanation for reproducing:
I'm using FSCalendar to display a calendar in my SwiftUI app. When a date is tapped I display a sheet on my CalendarView.swift
. I have events for some dates and when those dates are tapped I modify the sheet and display the details of the event. These events are CoreData entities called Birth
and they have a date (Date) and name (String) item inside. When the calendars didSelect
function is triggered I check if any event date matches the tapped date. After tapped the date is stored in a state at CalendarView.swift
called selectedDate
the state is sent to the CalendarViewRepresentable
as a binding. If the date matches any event date I set the selectedItems
state to the item in an array and add the other events to the selectedItems
array if it matched with more then one event. (FYI: I'm testing on IOS 17.5 both simulator and real device, CalendarView.swift
is inside a WindowGroup >> TabView)
Problem:
When a date is tapped in the calendar it doesn't update the selectedDate
and selectedItems
states the first time. They stay in their initial values (in my case nil is the initial value set on CalendarView.swift
). However when I tap again on the same date or some other date it starts working fine and shows the right date.
Codes:
CalendarView.swift
import SwiftUI
import CoreData
struct CalendarView: View {
@Environment(\.managedObjectContext) var viewContext
@State var entries: [Birth] = []
@State var selectedDate: Date? = nil
@State var selectedItems: [Birth]? = nil
@State var presentSelection: Bool = false
var body: some View {
CalendarViewRepresentable(items: $entries, selectedDate: $selectedDate, selectedItems: $selectedItems, presentSelection: $presentSelection)
.onAppear {
fetchBirths()
}
.sheet(isPresented: $presentSelection, content: {
if selectedItems != nil {
Text(selectedDate?.description ?? "Date not found")
Text(String(describing: selectedItems!.first!.name))
.presentationDetents([.height(170)])
.presentationCornerRadius(20)
} else {
Text(String(describing: selectedItems?.count))
Text(selectedDate?.description ?? "Date not found")
.presentationDetents([.height(170)])
.presentationCornerRadius(20)
}
})
}
private func fetchBirths() {
let request: NSFetchRequest<Birth> = Birth.fetchRequest()
request.relationshipKeyPathsForPrefetching = ["group"]
do {
let results = try viewContext.fetch(request)
self.entries = results
} catch {
print("Error fetching data: \(error)")
}
}
}
Calendar.swift
(CalendarViewRepresentable)
import SwiftUI
import FSCalendar
struct CalendarViewRepresentable: UIViewRepresentable {
@Environment(\.colorScheme) var colorScheme
typealias UIViewType = FSCalendar
var calendar = FSCalendar()
@Binding var items: [Birth]
@Binding var selectedDate: Date?
@Binding var selectedItems: [Birth]?
@Binding var presentSelection: Bool
func makeUIView(context: Context) -> FSCalendar {
calendar.delegate = context.coordinator
calendar.dataSource = context.coordinator
// Appearance
calendar.appearance.todayColor = UIColor(named: "todayBGColor")
calendar.appearance.titleTodayColor = .accent
calendar.appearance.titleFont = .boldSystemFont(ofSize: 16)
calendar.appearance.titleWeekendColor = .darkGray
calendar.appearance.selectionColor = UIColor(named: "selectionDateBG")
calendar.appearance.titleSelectionColor = UIColor(named: "selectionDateFG")
calendar.appearance.eventDefaultColor = .accent
calendar.appearance.eventSelectionColor = .accent
calendar.appearance.imageOffset = CGPoint(x: 0, y: 5)
calendar.appearance.eventOffset = CGPoint(x: 15.5, y: 3.5)
calendar.appearance.weekdayTextColor = .accent
calendar.appearance.borderRadius = 0.65
calendar.appearance.headerDateFormat = "MMMM"
calendar.appearance.headerMinimumDissolvedAlpha = 0.1
calendar.appearance.headerTitleFont = .systemFont(
ofSize: 27,
weight: .black)
calendar.appearance.headerTitleColor = .label
calendar.appearance.titleDefaultColor = .label
calendar.firstWeekday = 2
calendar.scrollDirection = .vertical
calendar.scope = .month
calendar.clipsToBounds = false
return calendar
}
func updateUIView(_ uiView: FSCalendar, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject,
FSCalendarDelegate, FSCalendarDataSource {
var parent: CalendarViewRepresentable
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) { // <<<<< SELECTION MADE HERE
parent.presentSelection = false
parent.selectedItems = nil
for item in parent.items {
if item.date! === date { // "===" Date extension in the code below
if parent.selectedItems == nil {
parent.selectedItems = [item]
} else {
parent.selectedItems?.append(item)
}
}
}
parent.selectedDate = date
parent.presentSelection = true
}
func calendar(_ calendar: FSCalendar,
numberOfEventsFor date: Date) -> Int { // Sets event dots
let today = Date()
let eventDates: [Date] = parent.items.map { item in
let daysLeft = daysToDate(date: item.date)
return Calendar.current.date(byAdding: .day, value: daysLeft, to: today)!
}
var eventCount = 0
eventDates.forEach { eventDate in
if eventDate.formatted(date: .complete,
time: .omitted) == date.formatted(
date: .complete, time: .omitted){
if eventCount != 2 {
eventCount += 1;
}
}
}
return eventCount
}
func calendar(_ calendar: FSCalendar,
imageFor date: Date) -> UIImage? {
var image: UIImage? = nil
let _: [Date?] = parent.items.map { item in
let daysLeft = daysToDate(date: item.date)
let itemDate = Calendar.current.date(byAdding: .day, value: daysLeft, to: Date())!
if itemDate === date {
var data = item.picture
if item.picture == nil {
data = Data()
}
image = data!.toSmallAvatarImage()
}
return nil
}
return image
}
func maximumDate(for calendar: FSCalendar) -> Date {
var dt = Calendar.current.dateComponents([.year, .month, .day], from: Date())
dt.day = Int(dt.day ?? 0) - 1
let start = Calendar.current.date(from: dt)!
return Calendar.current.date(byAdding: .month, value: 12, to: start)!
}
func minimumDate(for calendar: FSCalendar) -> Date {
let dt = Calendar.current.dateComponents([.year, .month], from: Date())
return Calendar.current.date(from: dt)!
}
init(_ parent: CalendarViewRepresentable) {
self.parent = parent
}
}
}
Date extension
static func ===(first: Date, second: Date) -> Bool {
let firstDate = Calendar.current.dateComponents([.month, .day], from: first)
let secondDate = Calendar.current.dateComponents([.month, .day], from: second)
return firstDate.day == secondDate.day && firstDate.month == secondDate.month
}
Upvotes: 2
Views: 62