Reputation: 87
I am customizing a UIViewCalendar and try to fetch data on it . If I used sample data it worked but when I used data from firebase , data fetched but not showing at the first time , only after I clicked the left-right button to switch between month in calendar (ex: move from November to October , November -> December)
Here is the fetch data method
@Published var appointments = [Appointment]()
@MainActor
func getAppointments() async throws {
do{
DispatchQueue.main.async{
self.calendarState = .loading
}
//result of compactMap is optional
//will change depend on initial variable
appointments = try await db.collection(Constants.appointmentCollection).getDocuments()
.documents.compactMap{
let id = $0.data()[Constants.idFieldInFirestore] as? String ?? ""
let userName = $0.data()[Constants.userNameFieldInFirestore] as? String ?? ""
let uid = $0.data()[Constants.uidFieldInFirestore] as? String ?? ""
let date = $0.data()[Constants.dateFieldInFirestore] as? Timestamp
let doctorName = $0.data()[Constants.doctorNameFieldInFirestore] as? String ?? ""
let isConfirm = $0.data()[Constants.isConfirmFieldInFirestore] as? Bool ?? false
let appointmentTitle = $0.data()[Constants.appointmentTitleFieldInFirestore] as? String ?? ""
let appointmentTypeValue = $0.data()[Constants.appointmentTypeFieldInFirestore] as? String ?? ""
'dd'T'HH':'mm':'ssZZZ"
let dateConverted = date?.dateValue()
print(dateConverted)
let appointment = Appointment(userName: userName, uid: uid, date: dateConverted!, isConfirm:isConfirm,doctorName: doctorName, appointmentTitle: appointmentTitle )
appointments.append(appointment)
return appointment
}
DispatchQueue.main.async{
for appointment in self.appointments{
//print(appointment.dateComponent)
}
print("appointment.dateComponent")
self.calendarState = .loadedList(self.appointments)
}
}catch{
DispatchQueue.main.async{
self.calendarState = .failed(error)
}
}
}
Here is the UICalendarView class
struct CalendarView:UIViewRepresentable{
let interval : DateInterval
@ObservedObject var appointmentStore : AppointmentStore
@Binding var dateSelected: DateComponents?
@Binding var displayEvents: Bool
var state = FirebaseDataSource.AddingState.idle
//implement listener event for data
func makeCoordinator() -> Coordinator {
Coordinator(parent: self, appointmentStore: appointmentStore)
}
func makeUIView(context: Context) -> UICalendarView {
let view = UICalendarView()
view.delegate = context.coordinator
view.calendar = Calendar(identifier: .gregorian)
view.availableDateRange = interval
let dataSelection = UICalendarSelectionSingleDate(delegate: context.coordinator)
view.selectionBehavior = dataSelection
return view
}
func updateUIView(_ uiView: UICalendarView, context: Context) {
if let changedAppointment = appointmentStore.changedAppointment{
uiView.reloadDecorations(forDateComponents: [changedAppointment.dateComponent], animated: true)
appointmentStore.changedAppointment = nil
}
if let movedAppointment = appointmentStore.movedAppointment{
uiView.reloadDecorations(forDateComponents: [movedAppointment.dateComponent], animated: true)
appointmentStore.movedAppointment = nil
}
}
typealias UIViewType = UICalendarView
class Coordinator : NSObject , UICalendarViewDelegate ,UICalendarSelectionSingleDateDelegate{
func dateSelection(_ selection: UICalendarSelectionSingleDate, didSelectDate dateComponents: DateComponents?) {
parent.dateSelected = dateComponents
guard let dateComponents else {return}
let foundAppointment = $appointmentStore.firebaseDataSource.appointments
.filter{$0.date.startOfDay.wrappedValue == dateComponents.date!.startOfDay}
if !foundAppointment.isEmpty{
parent.displayEvents.toggle()
}
}
func dateSelection(_ selection: UICalendarSelectionSingleDate, canSelectDate dateComponents: DateComponents?) -> Bool{
return true
}
var parent : CalendarView
@ObservedObject var appointmentStore : AppointmentStore
init(parent: CalendarView, appointmentStore: AppointmentStore) {
self.parent = parent
self.appointmentStore = appointmentStore
}
@MainActor
func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? {
print("item:")
print(appointmentStore.firebaseDataSource.appointments.count)
let foundAppointments = appointmentStore.firebaseDataSource.appointments
.filter{$0.date.startOfDay == dateComponents.date?.startOfDay }
if foundAppointments.isEmpty {
return nil}
if foundAppointments.count > 1 {
return .image(UIImage(systemName: ""),color: .red,
size: .large)
}
let singleAppointment = foundAppointments.first!
return .customView{
print("Run update");
let icon = UILabel()
icon.text = singleAppointment.appointmentType.icon
return icon
}
}
}
}
How can I trigger the update event of UICalendarView?
Upvotes: 0
Views: 310
Reputation: 87
I fixed it today after taking a look in Chris Wu 's article https://chriswu.com/posts/swiftui/uicalendarview/ I realized that my problem is I am currently nested the Published object.
My old flow :
FirebaseDataSource class(Created a list and fetching data)
-> AppointmentStore(Control data -update-delete....)
-> MainView(which contained UICalendarView)
I put my list in FirebaseDataSource instead of AppointmentStore so the MainView didn't get it .
My new flow :
FirebaseDataSource class(fetching data)
-> AppointmentStore(Created a list)
-> MainView(which contained UICalendarView)
Like this :
class AppointmentStore : ObservableObject{
//var appointments = [Appointment]()
@Published var changedAppointment: Appointment?
@Published var movedAppointment: Appointment?
@Published private(set) var addingState = FirebaseDataSource.AddingState.idle
@Published var appointments = [Appointment]()
var firebaseDataSource : FirebaseDataSource = FirebaseDataSource()
init() {
Task{
//TODO: fetch data from firebase
do{
//pass appointment here
appointments = try await self.firebaseDataSource.getAppointments()
}catch{
print(error.localizedDescription)
}
}
}
Upvotes: 0