fix: Liquid Glass month title as inline content strip (not the system toolbar)
The NavigationStack toolbar title never refreshes on month change on iOS 26 (4 approaches tried: principal Text, navigationTitle, @Observable store). The title now renders as a normal inline Text in a top safe-area inset just below the system glass toolbar — the same mechanism as the flat variant, which does update. The system toolbar keeps the buttons + the real Liquid Glass look. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -101,9 +101,10 @@ struct CalendarHostView: View {
|
|||||||
|
|
||||||
private var glassVariant: some View {
|
private var glassVariant: some View {
|
||||||
// Real iOS-26 Liquid Glass: the system NavigationStack toolbar renders the
|
// Real iOS-26 Liquid Glass: the system NavigationStack toolbar renders the
|
||||||
// glass bar. The title is driven by `navTitle` (@State) — updated via
|
// glass bar (buttons). The month TITLE is NOT placed in the toolbar — the
|
||||||
// onChange(of: titleString) and keyed with .id so the system bar reliably
|
// system title silently fails to refresh on month change on iOS 26 — but
|
||||||
// refreshes on month change (binding the computed title directly did not).
|
// as a normal inline Text in a top safe-area inset just below the glass
|
||||||
|
// bar, where it updates reliably (same mechanism as the flat variant).
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
calendarContent
|
calendarContent
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
@@ -112,9 +113,6 @@ struct CalendarHostView: View {
|
|||||||
loadingIndicator.padding(.top, 12)
|
loadingIndicator.padding(.top, 12)
|
||||||
}
|
}
|
||||||
.animation(.easeInOut(duration: 0.2), value: store.isLoading || store.isCachingBackground)
|
.animation(.easeInOut(duration: 0.2), value: store.isLoading || store.isCachingBackground)
|
||||||
.overlay(alignment: .top) {
|
|
||||||
if let err = store.lastError { errorBannerView(err).padding(.top, 8) }
|
|
||||||
}
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
@@ -124,14 +122,6 @@ struct CalendarHostView: View {
|
|||||||
Button(L10n.t("nav.today", appLang)) { store.moveToToday() }.font(.callout)
|
Button(L10n.t("nav.today", appLang)) { store.moveToToday() }.font(.callout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .principal) {
|
|
||||||
// Reads store.visibleMonth (@Observable) so the system
|
|
||||||
// toolbar refreshes on month change.
|
|
||||||
Text(titleString)
|
|
||||||
.font(.headline)
|
|
||||||
.lineLimit(1)
|
|
||||||
.minimumScaleFactor(0.7)
|
|
||||||
}
|
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
if !groups.isEmpty { groupMenu }
|
if !groups.isEmpty { groupMenu }
|
||||||
@@ -141,7 +131,19 @@ struct CalendarHostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.safeAreaInset(edge: .top) { groupBanner }
|
.safeAreaInset(edge: .top, spacing: 0) {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Text(titleString)
|
||||||
|
.font(.headline)
|
||||||
|
.lineLimit(1)
|
||||||
|
.minimumScaleFactor(0.7)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, 6)
|
||||||
|
.background(.bar)
|
||||||
|
groupBanner
|
||||||
|
errorBanner
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.overlay(alignment: .bottomTrailing) { glassFAB }
|
.overlay(alignment: .bottomTrailing) { glassFAB }
|
||||||
.modifier(calendarSheets)
|
.modifier(calendarSheets)
|
||||||
|
|||||||
Reference in New Issue
Block a user