1

I am building a calendar-like app that renders calendars month by month. The program contains several calendars (let's say 2 for the purpose of the question, calendar A and B) that store independent data. I have a @Published variable selectedCalendar defined in an ObservableObject class called EnvironmentClass() that determines which calendar the various Views should render.

class EnvironmentClass: ObservableObject {
    
    @Published var calendars: OrderedDictionary<String,CalendarClass> = CalendarClass.sampleData 
    @Published var date = Date() // Today's date
    @Published var selectedCalendar = "A"
}

here date holds todays date and calendars hold data about the calendars. In the home View there is a menu View from which one can change calendars, called CalendarsView. The issue is the following: if I run the app I see a calendar month for which all date cells belong to "A" (found with a print statement). When I change calendar to "B" from CalendarsView, what I see is that only a part of the date cells changes to "B", while some of them remain "A". By debugging with print statement I found out that, after receiving a new information in env, the app starts to render the date cells slightly before changing actually calendar in env.selectedCalendar.

Few extra info found from debugging:

  • The number of cells that remain "A" changes every time I launch the app. Sometimes it's just a couple, other times many.
  • The ForEach that renders the date cells (see calendarGrid below) when env.selectedCalendar changes to "B" does it in order but skips some (randomly). Those cells are the ones that will actually change to "B" and are rendered in order right after env.selectedCalendar changes.
  • If I press a second time the button that changes the View to "B", the month I see is correct in the sense that all the cells belong to "B". At this point, if I press "A" the same problem happens (with the same cells).

It looks like the information env.selectedCalendar = "B" is injected in calendarGrid (see below) with a little bit of delay. Is this the problem? If so, how does one fix it?

I attach the code for CalendarsView and calendarGrid.

import SwiftUI

struct CalendarsView: View {
    
    @EnvironmentObject var env: EnvironmentClass
    
    var body: some View {
        ScrollView (.horizontal, showsIndicators: true) {
            HStack (spacing: 30) {
                ForEach(env.calendars.keys, id: \.self) {index in
                    Button(action: {
                        env.selectedCalendar = index
                    }) {
                        Text(index)
                            .font(.headline)
                            .frame(maxWidth: .infinity, alignment: .leading)
                    }
                }
            }.padding(.horizontal, 35).padding(.bottom,50)
        }
    }
}

(here env.calendars.keys contains "A" and "B"). The actual calendar View is rendered using a LazyVGrid

var calendarGrid: some View {
        
        VStack{
            let columnLayout: [GridItem] = Array(repeating: GridItem(), count: 7)
            let firstDayOfMonth = CalendarHelper().firstOfMonth(env.date)
            let startingSpaces =  CalendarHelper().weekDay(CalendarHelper().minusDay(firstDayOfMonth))
            let currentMonth: [Date?] =  Array(repeating: nil, count: startingSpaces) + CalendarHelper().daysInMonthArray(env.date)

// I use currentMonth with few nil values to address the indentation of the weekdays 
// (if the month starts on Friday, I skip startingSpaces=4 date cells).
        
            LazyVGrid(columns: columnLayout) {
                ForEach(currentMonth.indices, id: \.self) { index in
                    if (currentMonth[index] != nil) {
                        DateCell(day: currentMonth[index]!)
                            .environmentObject(env)
                    }
                    else {
                        Text("")
                    }
                }
            }
        }.padding(.horizontal, 25)
    }

Any help is very appreciated.

Paul
  • 111
  • 2
  • Looking quickly at your code, the most suspicious part to me is that you're using incides of the `currentMonth` array as IDs for the cells. My first bet would be that when SwiftUI re-renders the view with a new month, it likely preserves some cells using their identity information. If you could provide a sample project with a minimal example, I'd take a deeper look. – Vadim Belyaev Jul 31 '22 at 14:46
  • Lazy variables and views are loaded only when needed. – cora Jul 31 '22 at 14:49
  • Instead of creating constants with let , it would be better to have computed var depending built using env (currentMonth). Using let may prevent SwiftUI to know it has to redraw. – Ptit Xav Jul 31 '22 at 16:35

0 Answers0