From 451d3d4d6bd5e7a6dd15b356b65f66cddd363911 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Mon, 1 Jun 2026 19:06:53 +0200 Subject: [PATCH] fix: Liquid Glass month title updates via @Observable store (visibleMonth) The system NavigationStack toolbar title would not refresh on a plain @State change (title kept disappearing on iPhone). Moved visibleMonth into the @Observable CalendarStore so the toolbar's read is tracked with @Observable's fine-grained observation and refreshes on month change. Reverted the @State/.id workaround. Real system glass bar retained. Co-Authored-By: Claude Opus 4.8 --- Calendarr iOS/Models/CalendarStore.swift | 4 ++++ .../Views/Calendar/CalendarHostView.swift | 21 +++++++------------ Calendarr iOS/Views/Calendar/MonthView.swift | 5 ++--- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Calendarr iOS/Models/CalendarStore.swift b/Calendarr iOS/Models/CalendarStore.swift index 6f924e2..720d085 100644 --- a/Calendarr iOS/Models/CalendarStore.swift +++ b/Calendarr iOS/Models/CalendarStore.swift @@ -51,6 +51,10 @@ class CalendarStore { var events: [CalEvent] = [] var viewType: CalViewType = .month var currentDate: Date = .now + // The month currently scrolled into view (month view). Lives in the store so + // the Liquid-Glass navigation title — read in the system toolbar — updates + // via @Observable tracking (a plain @State did not refresh the toolbar). + var visibleMonth: Date = .now var isLoading = false var isCachingBackground = false var lastError: String? = nil diff --git a/Calendarr iOS/Views/Calendar/CalendarHostView.swift b/Calendarr iOS/Views/Calendar/CalendarHostView.swift index b9298c9..572ca7b 100644 --- a/Calendarr iOS/Views/Calendar/CalendarHostView.swift +++ b/Calendarr iOS/Views/Calendar/CalendarHostView.swift @@ -27,21 +27,16 @@ struct CalendarHostView: View { @State private var store = CalendarStore() @State private var editorContext: CalEditorContext? = nil @State private var selectedEvent: CalEvent? = nil - @State private var visibleMonth: Date = .now @State private var showFilter = false @State private var didApplyDefaultView = false @State private var groups: [CalGroup] = [] - // Drives the Liquid-Glass navigation-bar title. Kept in @State and updated - // via onChange so the system toolbar reliably refreshes on month change - // (reading the computed title directly in the toolbar did not update). - @State private var navTitle = "" private var titleString: String { if store.viewType == .month { let f = DateFormatter() f.locale = L10n.locale(appLang) f.dateFormat = "LLLL yyyy" - return f.string(from: visibleMonth).capitalized(with: L10n.locale(appLang)) + return f.string(from: store.visibleMonth).capitalized(with: L10n.locale(appLang)) } return store.titleForCurrentView(language: appLang) } @@ -89,7 +84,7 @@ struct CalendarHostView: View { .onChange(of: store.currentDate) { _, _ in Task { await onNavigate() } } .onChange(of: store.viewType) { _, _ in Task { await onNavigate() } } .onChange(of: cacheMonths) { _, _ in Task { await recache() } } - .onChange(of: visibleMonth) { _, new in Task { await ensureLoaded(around: new) } } + .onChange(of: store.visibleMonth) { _, new in Task { await ensureLoaded(around: new) } } .onChange(of: scenePhase) { _, phase in if phase == .active { Task { await SettingsSync.pull(api: api) } } } .onReceive(NotificationCenter.default.publisher(for: .banishedCalendarsChanged)) { _ in store.syncBanishedFromDefaults() @@ -130,11 +125,12 @@ struct CalendarHostView: View { } } ToolbarItem(placement: .principal) { - Text(navTitle) + // Reads store.visibleMonth (@Observable) so the system + // toolbar refreshes on month change. + Text(titleString) .font(.headline) .lineLimit(1) .minimumScaleFactor(0.7) - .id(navTitle) // force re-creation so the bar refreshes } ToolbarItem(placement: .navigationBarTrailing) { HStack(spacing: 8) { @@ -150,12 +146,10 @@ struct CalendarHostView: View { .overlay(alignment: .bottomTrailing) { glassFAB } .modifier(calendarSheets) .task { await startup() } - .task { navTitle = titleString } - .onChange(of: titleString) { _, new in navTitle = new } .onChange(of: store.currentDate) { _, _ in Task { await onNavigate() } } .onChange(of: store.viewType) { _, _ in Task { await onNavigate() } } .onChange(of: cacheMonths) { _, _ in Task { await recache() } } - .onChange(of: visibleMonth) { _, new in Task { await ensureLoaded(around: new) } } + .onChange(of: store.visibleMonth) { _, new in Task { await ensureLoaded(around: new) } } .onChange(of: scenePhase) { _, phase in if phase == .active { Task { await SettingsSync.pull(api: api) } } } .onReceive(NotificationCenter.default.publisher(for: .banishedCalendarsChanged)) { _ in store.syncBanishedFromDefaults() @@ -332,8 +326,7 @@ struct CalendarHostView: View { onShowDay: { day in store.currentDate = day store.viewType = .day - }, - visibleMonth: $visibleMonth) + }) case .week: WeekView(store: store, onEventTap: { selectedEvent = $0 }, diff --git a/Calendarr iOS/Views/Calendar/MonthView.swift b/Calendarr iOS/Views/Calendar/MonthView.swift index 1a1f77b..d06813e 100644 --- a/Calendarr iOS/Views/Calendar/MonthView.swift +++ b/Calendarr iOS/Views/Calendar/MonthView.swift @@ -19,7 +19,6 @@ struct MonthView: View { let onCreateEvent: (Date) -> Void let onShowWeek: (Date) -> Void let onShowDay: (Date) -> Void - @Binding var visibleMonth: Date @AppStorage("appLanguage") private var appLang = "system" @AppStorage("monthDividerColor") private var dividerHex = "#7090c0" @@ -117,8 +116,8 @@ struct MonthView: View { private func publishVisibleMonth(from week: Date?) { guard let w = week else { return } let month = cal.date(from: cal.dateComponents([.year, .month], from: w)) ?? w - if visibleMonth != month { - visibleMonth = month + if store.visibleMonth != month { + store.visibleMonth = month } } }