Custom Calendar SwiftUI change color of selected day

I need to create this view calendar view

Im using just Foundation, UIKit and SwiftUI libraries to create the calendar.

I was able to create the calendar and put the functionalities it needs, but I can't make the color of the button of the day change when you select it.

This is the code I wrote

CalendarViewModel

class CalendarViewModel: ObservableObject {
@Published var calendarButtons  :[[CalendarButton]] = []
@Published var currentMonth     :Int = 2
@Published var monthString      :String = ""
@Published var errorMessage     :String = ""
@Published var selectedActivies :[CalendarModel.Activities] = []
@Published var selectedDay      :Int = 0
@Published var calendarM: CalendarModel? = .init( labels: CalendarModel.LabelCalendar.init(label_ir: "Ir", label_no_actividades: "No tienes actividades este dia", label_meses: CalendarModel.LabelMonth.init(ene: "enero", feb: "Febrero", mar: "Marzo", abr: "Abril", may: "Mayo", jun: "Junio", jul: "Julio", ago: "Agosto", sep: "Septiembre", oct: "Octubre", nov: "Noviembre", dic: "Diciembre")), actividades: [CalendarModel.Activities.init(tipo: 6, tipo_nombre: "Foro", periodo_nombre: "Sesión 1. Los orígenes del aprendizaje y la teoría de la mente", tema: "Contenido Temático 1", nombre: "Presentación Personal", descripcion: "Hola", fecha_inicio: "2020-02-17 00:00:00", fecha_termina: "2020-02-23 23:59:59", duracion_fechas: "17/02/2020 00:00 - 23/02/2020 23:59", duracion_fechas_modal: "17/02/2020 - 23/02/2020", duracion_dias: "7 dias", id_actividad: 3024198, status_descripcion: "Vencida")])



func getColor(status: String) -> Color { // This function just returns a color to where it is called
        switch status {
        case "Pendiente": return Color(red: 0.737, green: 0.275, blue: 0.478)
        case "Vencida": return Color(red: 0.827, green: 0.827, blue: 0.816)
        case "Entregada": return Color(red: 0.553, green: 0.808, blue: 0.565)
        case "Próxima": return Color(red: 0.827, green: 0.827, blue: 0.816)
        default: return Color.blue
        }
    }

func getCalendar(idCourse:Int,dateStart: String, dateEnd:String, getModel: ((_ calendar: CalendarModel?,_ err: Bool) -> Void)?) {
    
     let URL:String = "/curso/\(idCourse)/calendario?fecha_inicio=\(dateStart)&fecha_termina=\(dateEnd)"
    
     let headers: HTTPHeaders = [
         "Authorization": "Bearer \(Token.val)"
        ]
     
    RequestAF(route: URL, header: headers, typeMethod: .get, params: nil
        ,callback: {data, error in
         if data != nil{
          let dataResult = try! JSONDecoder().decode(CalendarModel.self, from: data!)
              DispatchQueue.main.async {
                self.calendarM = dataResult
                self.setCalendarBody()
                getModel!(self.calendarM, false)
              }
         } else {
             DispatchQueue.main.async {
                 guard let dataError = error else { return print("Fatal Error: CourseViewModel") }
                 self.errorMessage = dataError.message
                getModel!(nil, true)
             }
         }
     })
}

func resetCalendar() {
    self.calendarButtons = []
    self.setCalendarBody()
}
func getMonthString(day: Int) -> String {
    switch day {
        case 1:     return calendarM!.labels.label_meses.ene
        case 2:     return calendarM!.labels.label_meses.feb
        case 3:     return calendarM!.labels.label_meses.mar
        case 4:     return calendarM!.labels.label_meses.abr
        case 5:     return calendarM!.labels.label_meses.may
        case 6:     return calendarM!.labels.label_meses.jun
        case 7:     return calendarM!.labels.label_meses.jul
        case 8:     return calendarM!.labels.label_meses.ago
        case 9:     return calendarM!.labels.label_meses.sep
        case 10:    return calendarM!.labels.label_meses.oct
        case 11:    return calendarM!.labels.label_meses.nov
        case 12:    return calendarM!.labels.label_meses.dic
        default:    return ""
    }
}

func getNumberMonth(day: String) -> Int {
    switch day {
        case "sunday":      return 0
        case "monday":      return 1
        case "tuesday":     return 2
        case "wednesday":   return 3
        case "thursday":    return 4
        case "friday":      return 5
        case "saturday":    return 5
    default: return -1
    }
}

func setCalendarBody(){
        let stringDay = DateFormatter()
        let intDay = DateFormatter()
        let PreviusMounthFtt = DateFormatter()
        let yearFtt = DateFormatter()
    
    
    
        stringDay.dateFormat        = "EEEE"
        intDay.dateFormat           = "dd"
        PreviusMounthFtt.dateFormat = "MM"
        yearFtt.dateFormat          = "yyyy"
        
        let yearDate = Calendar.current.date(byAdding: .month, value: 0, to: Date())
        let yearValue = yearFtt.string(from: yearDate!)
        let calendar = Calendar.current
        let dateComponent = DateComponents(year: Int(yearValue)!, month: self.currentMonth)
        let newDate = calendar.date(from: dateComponent)!

        let comp: DateComponents = Calendar.current.dateComponents([.year, .month], from: newDate)
        let startOfMonth = Calendar.current.date(from: comp)!
        var comps2 = DateComponents()
        comps2.month = 1
        comps2.day = -1
        let endOfMonth = Calendar.current.date(byAdding: comps2, to: startOfMonth)
        
        let datePrevMonth = Calendar.current.date(byAdding: .month, value: -1, to: newDate)
        let numberMonth = PreviusMounthFtt.string(from: datePrevMonth!)
        let newComponent = DateComponents(year: Int(yearValue)!, month: Int(numberMonth))
        let releaseDate = calendar.date(from: newComponent)!
        let range = calendar.range(of: .day, in: .month, for: releaseDate)!
        
        
        let startDayOfMount = stringDay.string(from: startOfMonth)
        let lastDay = intDay.string(from: endOfMonth!)
        let daysPrevMonth = range.count
        
        print("-------------")
        self.monthString = startDayOfMount
        print("Day: "+startDayOfMount)
        print("LastDay: "+lastDay)
        print("PreviusDays: \(daysPrevMonth)")
        print("-------------")
        
        let numberDay = getNumberMonth(day: startDayOfMount.lowercased())
        var count:Int = 1
        var isStared: Bool = false
        var countExtraDays: Int = 0
        let previus = daysPrevMonth
        var newCuont: Int = 1
        let checkMonth = self.currentMonth
        for _ in 0...5 {
            var buttons: [CalendarButton] = []
            var newDays: [Int] = []
            print("currentMonth: \(checkMonth)")
            for j in 0...6 {
                print("count: \(count)")
                if isStared {
                    if count > Int(lastDay)! { // ESTE IF AGREGA LOS DIAS QUE SE ALCANZAN A VER DEL PROXIMO MES.
                        countExtraDays += 1
                        
                        buttons.append(CalendarButton.init(day: countExtraDays, stateDisable: false, activities: []
                            ,onCallBack: { acts, day in
                            self.selectedActivies = acts
                            self.selectedDay = day
                        }))
                        print("countExtraDays  : \(countExtraDays)")
                        newDays.append(countExtraDays)
                        continue
                    }
                    if count == self.getCurrentDay() && self.currentMonth == self.getCurrentMonth() {
                        print("count dia actual",count)
                        print("self.getCurrentDay \(self.getCurrentDay())")
                         self.selectedDay = count
                         buttons.append(CalendarButton.init(day: count, stateDisable: true, activities: getActivities(day: count, check: checkMonth)
                            , selectedDay: count
                           ,onCallBack: {acts, day in
                            print("day en button dia actual")
                           self.selectedActivies = acts
                           self.selectedDay = day
                            
                       }))
                    } else {
                       
                        buttons.append(CalendarButton.init(day: count, stateDisable: true, activities: getActivities(day: count, check: checkMonth)
                            ,onCallBack: {acts, day in
                            //    print("acts: ",acts)
                                print("day append: \(day)")
                            self.selectedActivies = acts
                            self.selectedDay = day
                                print("selectedDay en append: \(self.selectedDay)")
                        }))
                    }
                    
                    
                    newDays.append(count)
                    count += 1
                } else {  // END IF isSTARED
                    if numberDay == j {
                        isStared = true
                        print("isStared true")
                        if count == self.getCurrentDay() && self.currentMonth == self.getCurrentMonth() {
                            self.selectedDay = count
                            buttons.append(CalendarButton.init(day: count, stateDisable: true, activities: getActivities(day: count, check: checkMonth)
                                ,selectedDay: count
                                ,onCallBack: {acts, day in
                                self.selectedActivies = acts
                                self.selectedDay = day
                            }))
                        } else {
                            buttons.append(CalendarButton.init(day: count, stateDisable: true, activities: getActivities(day: count, check: checkMonth)
                                ,onCallBack: {acts, day in
                                self.selectedActivies = acts
                                self.selectedDay = day
                            }))
                        }
                        
                        
                        newDays.append(count)
                        count += 1
                    } else {
                        let dat = (previus - (numberDay - newCuont))
                        print("newcuont \(newCuont)")
                        buttons.append(CalendarButton.init(day: dat, stateDisable: false, activities: []
                            ,onCallBack:{ acts,day in
                                print("555")
                            self.selectedActivies = acts
                            self.selectedDay = day
                        }))
                        
                        newDays.append(dat)
                        newCuont += 1
                    }
                }
            }
            print("newDays: \(newDays)")
            self.calendarButtons.append(buttons)
            //print("calendarButtons: \(self.calendarButtons)")
        }
       
}

func getActivities(day:Int,check: Int) -> [CalendarModel.Activities] {
    var newArrModel: [CalendarModel.Activities] = []
    let setCurrent = "\(getCurrentYear())-\(currentMonth)-\(day)"
    
    for i in self.calendarM!.actividades {
        let getStart = getFirstRange(stringStart: i.fecha_inicio)
        let getEnd = getSecondRange(stringEnd: i.fecha_termina)
        let startDate = convertStringToDate(string: getStart)
        let endDate = convertStringToDate(string: getEnd)
        let currentDate = convertStringToDate(string: setCurrent)
        let stateCheck = (startDate ... endDate).contains(currentDate)
        if stateCheck {
            newArrModel.append(i)
        }
        
    }
    print("newArrModel", newArrModel)
    return newArrModel
}

func convertStringToDate(string: String) -> Date {
    let isoDate = string
    let dateFormatter = DateFormatter()
    dateFormatter.locale = Locale(identifier: "en_US_POSIX")
    dateFormatter.dateFormat = "yyyy-MM-dd"
    let date = dateFormatter.date(from:isoDate)!
   return date
}

func getFirstRange(stringStart: String) -> String {
    let myString = stringStart
    let arrByString = myString.components(separatedBy: " ")
    return arrByString[0]
}

func getSecondRange(stringEnd: String) -> String {
    let mString2 = stringEnd
    let arrByString2 = mString2.components(separatedBy: " ")
    return arrByString2[0]
}

func getCurrentYear() -> String {
    let yearFtt = DateFormatter()
    yearFtt.dateFormat       = "yyyy"
    let yearDate = Calendar.current.date(byAdding: .month, value: 0, to: Date())
    let yearValue = yearFtt.string(from: yearDate!)
    return yearValue
}
func getCurrentMonth() -> Int {
    let monthFtt = DateFormatter()
    monthFtt.dateFormat       = "MM"
    let monthDate = Calendar.current.date(byAdding: .month, value: 0, to: Date())
    let monthValue = monthFtt.string(from: monthDate!)
    return Int(monthValue)!
}
func getCurrentDay() -> Int {
    let dayFtt = DateFormatter()
    dayFtt.dateFormat       = "dd"
    let dayDate = Calendar.current.date(byAdding: .month, value: 0, to: Date())
    let dayValue = dayFtt.string(from: dayDate!)
    return Int(dayValue)!
}

}

CalendarButton

struct CalendarButton: Identifiable,View {
var id              = UUID()
var day             :Int
var stateDisable    :Bool
var activities      :[CalendarModel.Activities]
var isSelected: Bool = false

@State var selectedDay:Int = 0

var onCallBack: ((_ activity: [CalendarModel.Activities],_ day: Int) -> Void)

var body: some View {
    ZStack{
        if !stateDisable {
            Button(action: {
                print("                                       ")
                print("::::::::::::::::::::::::::::::::::::::")
                print("1st button de stateDisable \(stateDisable)")
                print("selectedDay \(self.selectedDay)")
                print("self.day \(self.day)")
                print("::::::::::::::::::::::::::::::::::::::")
                print("                                       ")
                
            }, label: {
                  Circle()
                   .fill(Color.clear)
                    .frame(width:40,height: 40)
                    .overlay(
                        HStack{
                        Text("\(self.day)")
                            .font(.system(size: 14))
                            .foregroundColor(.gray)
                    }).disabled(true)
            })
        } else if activities.isEmpty && stateDisable {
            Button(action: { //boton sin actividad en el dia y ¿¿se esta marcando aqui el dia actual??
                print("                                       ")
                print("::::::::::::::::::::::::::::::::::::::")
                print("2nd button")
                print("button de stateDisable \(stateDisable)")
                print("selectedDay  \(self.selectedDay)")
                print("self.day \(self.day)")
                self.selectedDay = self.day
                print("selectedDay after \(self.selectedDay)")
                print("self.day after \(self.day)")
                print("::::::::::::::::::::::::::::::::::::::")
                print("                                       ")
                self.onCallBack([],self.day)
            }, label: {

                Circle()
                    .fill(self.selectedDay == self.day  ? Color.darkBlue : Color.clear)
                    //.fill(self.selectedDay == day ? Color.clear : Color.darkBlue)
                    .frame(width:40,height: 40)
                    .overlay(HStack{
                        Text("\(self.day)")
                            .font(.system(size: 14))
                            .foregroundColor(self.selectedDay == self.day ? Color.white : Color.black)
                    })
            })
        }
        else {
            // boton con actividad en el dia
             Button(action: {
                print(".                                       .")
                print("::::::::::::::::::::::::::::::::::::::")
                print("3rd button")
                print("button de stateDisable \(stateDisable)")
                print("selectedDay \(self.selectedDay)")
                print("self.day before\(self.day)")
                self.selectedDay = self.day
                print("selectedDay after\(self.selectedDay)")
                print("self.day after \(self.day)")
                print("::::::::::::::::::::::::::::::::::::::")
                print(".                                       .")
                self.onCallBack(self.activities,self.day)
                print("selectedDay afterrrr\(self.selectedDay)")
             }, label: {
               Circle()
                .fill(self.selectedDay == self.day ? Color.darkBlue : Color.darkYellow)
               .frame(width:40,height: 40)
               .overlay(HStack{
                   Text("\(self.day)")
                       .font(.system(size: 14))
                       .foregroundColor(.white)
               })
            })
         }
    }
}

}

This is the calendar View:

struct CalendarView: View {

var calendarVM:CalendarViewModel
var calendarM:CalendarModel

var numbers:[String] = ["D","L","M","M","J","V","S"]

@State var activities: [CalendarModel.Activities] = [.init(tipo: 6, tipo_nombre: "Foro", periodo_nombre: "Sesión 1. Los orígenes del aprendizaje y la teoría de la mente", tema: "Contenido Temático 1", nombre: "Presentación Personal", descripcion: "¡Hola!, Bienvenido a tu curso de Aprendizaje y Memoria. Para poder conocerte de mejor forma, es necesario que te presentes ante tu docente y tus compañeros. Por favor indica en el foro lo siguiente. • Nombre completo• Edad• Programa académico• Qué esperas de esta asignatura.• Subir una fotografía ¡Es muy importante que cuides tu ortografía! ¡Éxito!", fecha_inicio: "2020-02-17 00:00:00", fecha_termina: "2020-02-23 23:59:59", duracion_fechas: "17/02/2020 00:00 - 23/02/2020 23:59", duracion_fechas_modal: "17/02/2020 - 23/02/2020", duracion_dias: "7 dias", id_actividad: 3024198, status_descripcion: "Pendiente")]
    
var router: RouterViewModel
@State var showModal = false
@State var modalActivity : CalendarModel.Activities? = nil

var body: some View {

    ZStack{
        ScrollView{
            VStack{
                MounthButtons(calendar: self.calendarM, calendarVM: self.calendarVM)
                VStack{
                    HStack{
                        ForEach(numbers,id:\.self){val in
                            HStack{
                                Text("\(val)").bold()
                                   .font(.system(size: 12))
                                  .foregroundColor(.darkPink)
                            }.frame(width:40,height: 47)
                                .background(Color.white).cornerRadius(16)
                            .shadow(color: Color("LightShadow"),
                            radius: 10, x: 0, y: 0)
                        }
                    }.padding(.bottom, 5)
                    ForEach(calendarVM.calendarButtons.indices,id: \.self){val in
                        VStack {
                            RowCalendar(buttons: self.calendarVM.calendarButtons[val] )
                        }
                    }
                }
                VStack{
                    HStack {
                        Text("\(calendarVM.getMonthString(day: self.calendarVM.currentMonth)) "+" \(calendarVM.selectedDay)")
                        .font(.custom("OpenSans-Regular",size: 18))
                            .foregroundColor(Color("BlackAYAText"))
                     
                         Spacer()
                    }
                    VStack{
                        HStack {Spacer()}.padding(.horizontal)
                        if self.calendarVM.selectedActivies.isEmpty {
                           HStack{
                                
                               Text(calendarM.labels.label_no_actividades)
                                   .font(.custom("OpenSans-Regular",size: 14))
                                .foregroundColor(Color("BlackAYAText"))
                                Spacer()
                               }.padding()
                               .background(Color.white)
                               .cornerRadius(18)
                            
                       }
                         ScrollView{
                            ForEach(self.calendarVM.selectedActivies){ act in
                                rowActivity(activity: act, onClick:{ act in
                                    modalActivity = act
                                    showModal.toggle()
                                }, calendarVM: self.calendarVM).padding(.bottom,10)
                                        .sheet(isPresented: $showModal){
                                            VStack{
                                                Spacer()
                                                ModalCalendar(router: self.router,activity: modalActivity!, labelIr: "Ir", homeCourse: false, onClick : {
                                                    modalActivity = nil
                                                    self.showModal.toggle()
                                                }).clearModalBackground()
                                                Spacer()
                                            }.padding()
                                            //.background(Color("ModalBackground"))
                                        }
                
                            }
                        }.shadow(color: Color("LightShadow"),
                        radius: 10, x: 0, y: 0)
                    }.frame(height: UIScreen.main.bounds.height/4)
                }.padding().padding(.horizontal)
            }
        }


        
    }
    
}

}

RowCalendar

struct RowCalendar: View {
var buttons:[CalendarButton]
var body: some View {
    HStack{
        ForEach(buttons.indices,id: \.self){val in
            self.buttons[val].body
        }
    }
}

}

RowActivity

struct rowActivity: View {
var activity: CalendarModel.Activities
var onClick:((_ act: CalendarModel.Activities) -> Void)?
var calendarVM:CalendarViewModel
var body: some View {
    HStack{
        Spacer()
        Button(action: {
            self.onClick!(self.activity)
        }, label: {
            HStack {
                VStack(alignment:.leading) {
                    Text(activity.nombre)
                        .font(.system(size: 14))
                    Text(activity.duracion_dias)
                        .font(.system(size: 11))
                        .foregroundColor(.gray)
                }
                Spacer()
                HStack {
                    Text("\(activity.status_descripcion)")
                        .foregroundColor(.white)
                        .font(.system(size: 12))
                        .fontWeight(.semibold)
                }.padding(.horizontal)
                    .padding(.vertical,10)
                .background(calendarVM.getColor(status: activity.status_descripcion))
                .cornerRadius(18)
                }.padding()
            .background(Color.white)
            .cornerRadius(16)
        }).buttonStyle(PlainButtonStyle())
        Spacer()
    }
}

}

MounthButtons

struct MounthButtons: View {
var calendar:CalendarModel
@ObservedObject  var calendarVM:CalendarViewModel

var body: some View {
    HStack{
        Button(action: {
            if self.calendarVM.currentMonth > 1 {
                self.calendarVM.currentMonth -= 1
            }
            self.calendarVM.resetCalendar()
        }, label: {
            Image("arrow")
            .resizable()
            .scaledToFit()
            .frame(width: 18, height: 18)
                .foregroundColor(Color.darkYellow)
              .rotationEffect(.degrees(180))
        }).padding()
        
        Text("\(calendarVM.getMonthString(day: self.calendarVM.currentMonth).uppercased())  \(calendarVM.getCurrentYear())")
            .font(.custom("OpenSans-SemiBold", size: 14))
            .foregroundColor(.darkYellow)
            
        
        Button(action: {
            if self.calendarVM.currentMonth < 12 {
                self.calendarVM.currentMonth += 1
            }
             self.calendarVM.resetCalendar()
        }, label: {
            Image("arrow")
            .resizable()
            .scaledToFit()
            .frame(width: 18, height: 18)
                .foregroundColor(Color.darkYellow)
        }).padding()
    }.padding(.bottom,1)
}

}



Read more here: https://stackoverflow.com/questions/66995980/custom-calendar-swiftui-change-color-of-selected-day

Content Attribution

This content was originally published by GbRuiz at Recent Questions - Stack Overflow, and is syndicated here via their RSS feed. You can read the original post over there.

%d bloggers like this: