crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

commit 11e8e8bcb46ef0f3ef6916736e568315541e18e3
parent 27076c6a1ea6bf4ee674a7a513ec26ad71601ca1
Author: Michael Camilleri <[email protected]>
Date:   Sun,  2 Aug 2026 21:49:38 +0900

Improve handling of six-week displays in the Puzzle Picker

The Puzzle Picker sheet on the iPad is a fixed-height form sheet, and a
month spanning six weeks — August 2026, for instance — left the calendar
too tall to fit properly. Each day cell claimed a fixed 44pt, so the
extra week pushed the confirm and random buttons off the bottom of the
sheet, out of the user's reach.

This commit makes the calendar the elastic part of the layout. The grid
is laid out over a constant six rows whose cells stretch to share
whatever height is left once the headers and the buttons have taken
theirs, sitting at their natural size where there is room and
compressing towards a 36pt floor where there is not. The cells span the
full column width, which keeps the tap target comfortable at either
size.

Holding the row count constant also settles the grid: a short month
gains a trailing blank row rather than the calendar changing height as
the user pages from one month to the next.

Co-Authored-By: Claude Opus 5 <[email protected]>

Diffstat:
MCrossmate/Views/Browse/CalendarDayCell.swift | 6+++++-
MCrossmate/Views/Browse/NYTBrowseView.swift | 61+++++++++++++++++++++++++++++++++++++++++--------------------
2 files changed, 46 insertions(+), 21 deletions(-)

diff --git a/Crossmate/Views/Browse/CalendarDayCell.swift b/Crossmate/Views/Browse/CalendarDayCell.swift @@ -14,7 +14,11 @@ struct CalendarDayCell: View { Text("\(dayNumber)") .font(.body) .fontWeight(isToday || isSelected ? .bold : .regular) - .frame(maxWidth: .infinity, minHeight: 44) + // A floor rather than a fixed height: the row lets the cell + // stretch when there is room and compresses it towards 36 when + // six weeks have to fit a fixed-height sheet. The full column + // width keeps the tap target comfortable either way. + .frame(maxWidth: .infinity, minHeight: 36, maxHeight: .infinity) .foregroundStyle(foregroundColor) .background { ZStack { diff --git a/Crossmate/Views/Browse/NYTBrowseView.swift b/Crossmate/Views/Browse/NYTBrowseView.swift @@ -89,9 +89,13 @@ struct NYTBrowseView: View { // of Monday … Sunday, with "Any Day" (declared first) pinned to the bottom. private static let weekdayMenuOrder = [1, 7, 6, 5, 4, 3, 2] - private let columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: 4), count: 7) private let weekdaySymbols = ["S", "M", "T", "W", "T", "F", "S"] + /// Every month is laid out over six rows, so the grid neither jumps height + /// as you page months nor overflows a fixed-height iPad form sheet in the + /// months that genuinely need six. + private static let gridRowCount = 6 + var body: some View { Group { switch nytAuth.sessionState { @@ -129,7 +133,6 @@ struct NYTBrowseView: View { monthHeader weekdayHeader dayGrid - Spacer() confirmButton randomButton } @@ -238,26 +241,36 @@ struct NYTBrowseView: View { } } + /// The rows share whatever vertical space the sheet has left over, rather + /// than each claiming a fixed height: on a roomy layout they sit at their + /// natural size, and in a 620pt iPad form sheet they compress instead of + /// pushing the buttons off the bottom. private var dayGrid: some View { - LazyVGrid(columns: columns, spacing: 4) { - ForEach(Array(gridCells.enumerated()), id: \.offset) { _, cell in - if let date = cell { - let cal = Self.nytCalendar - let dayNumber = cal.component(.day, from: date) - CalendarDayCell( - date: date, - dayNumber: dayNumber, - isEnabled: isEnabled(date), - isToday: cal.isDateInToday(date), - isSelected: isSelected(date), - isInLibrary: isInLibrary(date), - onTap: { selectedDate = date } - ) - } else { - Color.clear.frame(minHeight: 44) + Grid(horizontalSpacing: 4, verticalSpacing: 4) { + ForEach(Array(gridRows.enumerated()), id: \.offset) { _, row in + GridRow { + ForEach(Array(row.enumerated()), id: \.offset) { _, cell in + if let date = cell { + let cal = Self.nytCalendar + let dayNumber = cal.component(.day, from: date) + CalendarDayCell( + date: date, + dayNumber: dayNumber, + isEnabled: isEnabled(date), + isToday: cal.isDateInToday(date), + isSelected: isSelected(date), + isInLibrary: isInLibrary(date), + onTap: { selectedDate = date } + ) + } else { + Color.clear + } + } } + .frame(maxHeight: .infinity) } } + .frame(maxHeight: .infinity) } private var confirmButton: some View { @@ -518,12 +531,13 @@ struct NYTBrowseView: View { // MARK: - Grid private var gridCells: [Date?] { + let cellCount = Self.gridRowCount * 7 var cells: [Date?] = [] let cal = Self.nytCalendar let monthComps = cal.dateComponents([.year, .month], from: displayedMonth) guard let firstOfMonth = cal.date(from: monthComps), let range = cal.range(of: .day, in: .month, for: firstOfMonth) else { - return cells + return Array(repeating: nil, count: cellCount) } let firstWeekday = cal.component(.weekday, from: firstOfMonth) let leadingBlanks = firstWeekday - 1 @@ -531,10 +545,17 @@ struct NYTBrowseView: View { for day in range { cells.append(cal.date(byAdding: .day, value: day - 1, to: firstOfMonth)) } - while cells.count % 7 != 0 { cells.append(nil) } + while cells.count < cellCount { cells.append(nil) } return cells } + private var gridRows: [[Date?]] { + let cells = gridCells + return stride(from: 0, to: cells.count, by: 7).map { start in + Array(cells[start..<(start + 7)]) + } + } + private func isEnabled(_ date: Date) -> Bool { let cal = Self.nytCalendar let dayStart = cal.startOfDay(for: date)