I have a simple view that displays all days of a month like the apple calendar app does. The view is rendered for each month. In the view I show circles (BubbleView) that function as buttons. When I click a bubble, a sheet is opened. Either the creation, or the edit view.
From January (int 1) to March (int 3) it works perfectly fine. But from April (int 4) onwards, the bubbles I tap won't open my sheet. Can anybody give me a hint why my sheet might not be opening?
I'm new to SwiftUI, so please excuse my probably not very pretty code. I'm just getting a hang of it.
I've been trying all day. First I thought it might be a problem with daylight saving time. So I changed my calendar. Each day now has a timestamp from 00:00:00. So that should not be the problem. I'm at a point where I don't even know what I should google.
I've also checked if my bool value to open the sheet is true. It is. I also reach the else part of my function each time. Just the sheet does not open.
Since I am new to SwiftUI, I don't really know how to debug something like this. I've tried print statements and breakpoints. It all behaves exactly identical. I... I don't know.
Help would be very much appreciated.
Here is the code for my month view:
import SwiftUI
import SwiftData
struct CalendarMonthView: View {
@Query(sort: \CalendarDay.date) var days: [CalendarDay]
@State private var selectedDate: Date = getStartAndEndDatesForWeek()[0].date
@State private var selectedSavedDate: CalendarDay?
@State var isSheetPresenting = false
var month: Int
var year: Int
init(month: Int, year: Int) {
self.month = month
self.year = year
_days = Query(
filter: #Predicate<CalendarDay> { day in
day.monthNumber == month && day.yearNumber == year
},
sort: \CalendarDay.date
)
}
var body: some View {
VStack {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 7), spacing: 20) {
ForEach(fetchDatesForMonth(month: month)) { value in
let savedDay = days.first(where: { $0.date == value.date })
ZStack {
if value.date < getMaxDate() {
Button(action: {
print(value.date)
handleBubbleTap(savedDay, value.date)
}){
DayBubbleView(date: value.date, color: getBubbleColor(day: savedDay), opacity: getBubbleOpacity(day: savedDay))
}
.buttonStyle(PlainButtonStyle())
} else {
Text("")
}
}
.frame(width: 32, height: 32)
}
}
}
.sheet(isPresented: $isSheetPresenting) {
DayCreateView(date: $selectedDate)
}
.sheet(item: $selectedSavedDate) { date in
DayEditView(day: date)
}
}
func handleBubbleTap(_ savedDay: CalendarDay?, _ date: Date) {
selectedDate = date
print(selectedDate)
if (savedDay != nil) {
selectedSavedDate = savedDay
} else {
isSheetPresenting = true
print(isSheetPresenting)
}
}
func fetchDatesForMonth(month: Int) -> [WeekDate] {
let genericTimeZone = TimeZone.init(secondsFromGMT: 0)!
var genericCalendar = Calendar(identifier: .gregorian)
genericCalendar.timeZone = genericTimeZone
genericCalendar.locale = NSLocale(localeIdentifier: "de_DE") as Locale
genericCalendar.firstWeekday = 2
var dates = generateDatesForMonth(month: month) ?? []
let firstDayOfWeek = (genericCalendar.component(.weekday, from: dates.first?.date ?? Date()) - genericCalendar.firstWeekday + 7) % 7
for _ in 0..<firstDayOfWeek {
dates.insert(WeekDate(date: getMaxDate()), at: 0)
}
return dates
}
func generateDatesForMonth(month: Int) -> [WeekDate]? {
let genericTimeZone = TimeZone.init(secondsFromGMT: 0)!
var genericCalendar = Calendar(identifier: .gregorian)
genericCalendar.timeZone = genericTimeZone
genericCalendar.locale = NSLocale(localeIdentifier: "de_DE") as Locale
genericCalendar.firstWeekday = 2
let currentYear = genericCalendar.component(.year, from: Date())
// Create a date component with the specified month and year
var dateComponents = DateComponents()
dateComponents.year = currentYear
dateComponents.month = month
// Get the first day of the specified month
guard let firstDayOfMonth = genericCalendar.date(from: dateComponents) else {
return nil
}
// Get the range of days in the specified month
guard let range = genericCalendar.range(of: .day, in: .month, for: firstDayOfMonth) else {
return nil
}
// Generate an array of dates for the entire month
let dates = (range.lowerBound..<range.upperBound).compactMap { day in
var components = DateComponents()
components.year = currentYear
components.month = month
components.day = day
return WeekDate(date: genericCalendar.date(from: components)!)
}
return dates
}
func getMaxDate() -> Date {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd HH:mm"
return formatter.date(from: "9999/01/01 04:20")!
}
}
#Preview {
CalendarMonthView(month: 5, year: 2025)
}
And here is the code of the parent, that renders the month view 12 times. The idea is a similar layout as the activity app from apple.
import SwiftUI
import SwiftData
struct YearView: View {
var body: some View {
ScrollViewReader { value in
ScrollView {
LazyVStack(alignment: .leading,spacing: 0, pinnedViews: [.sectionHeaders]) {
ForEach(0..<12) { val in
Section(header: SectionHeaderView(month: val + 1)) {
CalendarMonthView(month: val + 1, year: getYearNumber(date: Date.now))
.padding()
}
}
}
}
}
.navigationBarTitle(Text(""), displayMode: .inline)
}
}
#Preview {
YearView()
}
Edit: Looks like it has something to do with the LazyVStack. I just changed my list to just render April through December and it turns out my sheet just simply won't open any item past the third one in my list.
The problem was that my MonthView was rendered inside a LazyVStack and all months not initially visible weren't showing the sheets for bubble taps.
With the comments pointing me in the right direction, I refactored my component to take my sheets out of the MonthView component and put them into the YearView component, since this is the view I want to lay my sheets over.