Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DON-1089: Fixing a scroll issue in BPKCalendar #2158

Closed
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions Backpack-SwiftUI/Calendar/Classes/BPKCalendar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public struct BPKCalendar<DayAccessoryView: View>: View {
private let showFloatYearLabel: Bool
private let dayAccessoryView: (Date) -> DayAccessoryView
@State private var currentlyShownMonth: Date
@State private var headerHeight: CGFloat?

public init(
selectionType: CalendarSelectionType,
Expand Down Expand Up @@ -80,15 +81,7 @@ public struct BPKCalendar<DayAccessoryView: View>: View {
validRange: validRange,
monthScroll: initialMonthScroll,
monthHeader: { monthDate in
CalendarMonthHeader(
monthDate: monthDate,
dateFormatter: monthHeaderDateFormatter,
calendar: calendar,
validRange: validRange,
accessoryAction: accessoryAction,
currentlyShownMonth: $currentlyShownMonth,
parentProxy: calendarProxy
)
headerView(monthDate: monthDate, calendarProxy: calendarProxy)
},
dayAccessoryView: dayAccessoryView
)
Expand All @@ -100,6 +93,22 @@ public struct BPKCalendar<DayAccessoryView: View>: View {
}
}

private func headerView(monthDate: Date, calendarProxy: GeometryProxy) -> some View {
CalendarMonthHeader(
monthDate: monthDate,
dateFormatter: monthHeaderDateFormatter,
calendar: calendar,
validRange: validRange,
accessoryAction: accessoryAction,
currentlyShownMonth: $currentlyShownMonth,
parentProxy: calendarProxy
)
.frame(height: headerHeight)
.modifier(ReadSizeModifier {
headerHeight = max($0.height, headerHeight ?? 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about having the initial headerHeight as 0 instead of optional nil ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answered to that below

})
}

private var yearBadge: some View {
VStack {
CalendarBadge(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ struct DefaultCalendarDayCell: View {
BPKText("\(calendar.component(.day, from: date))", style: .label1)
.lineLimit(1)
.padding(.vertical, .md)
.frame(maxWidth: .infinity)
}
}
160 changes: 100 additions & 60 deletions Backpack-SwiftUI/Calendar/Classes/Core/CalendarMonthGrid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@

import SwiftUI

private enum DayCellType {
case day
case leadingEmpty
case trailingEmpty
}

private struct DayCellIdentifiable: Identifiable {
let id: String
let index: Int
let type: DayCellType
}

struct CalendarMonthGrid<
DayCell: View,
EmptyLeadingDayCell: View,
Expand All @@ -28,7 +40,8 @@ struct CalendarMonthGrid<
let calendar: Calendar
let validRange: ClosedRange<Date>

@State private var dayCellHeight: CGFloat = 0
@State private var dayCellHeight: CGFloat?
@State var accessoryViewHeight: CGFloat?
@ViewBuilder let dayCell: (Date) -> DayCell
@ViewBuilder let emptyLeadingDayCell: () -> EmptyLeadingDayCell
@ViewBuilder let emptyTrailingDayCell: () -> EmptyTrailingDayCell
Expand All @@ -44,85 +57,112 @@ struct CalendarMonthGrid<

LazyVGrid(
columns: Array(repeating: GridItem(spacing: BPKSpacing.none.value), count: daysInAWeek),
spacing: BPKSpacing.lg.value
spacing: BPKSpacing.md.value
) {
// Create cells for the days from the previous month that are shown in the first week of the current month.
previousEmptyCells(daysFromPreviousMonth: daysFromPreviousMonth)
let numberOfDaysInMonth = calendar.range(of: .day, in: .month, for: monthDate)!.count
// Create cells for the days in the current month
currentMonthDayCell(numberOfDaysInMonth: numberOfDaysInMonth)

// Create cells for the days from the next month that are shown in the last week of the current month
// The total number of cells used is the sum of the number of days in the current month and the number of
// days from the previous month that are shown
let totalCellsUsed = numberOfDaysInMonth + daysFromPreviousMonth
let remainingCells = daysInAWeek - (totalCellsUsed % daysInAWeek)

remainingEmptyCells(remainingCells: remainingCells)
let remainingCells = if (daysInAWeek - (totalCellsUsed % daysInAWeek)) == daysInAWeek {
0
} else {
daysInAWeek - (totalCellsUsed % daysInAWeek)
}

currentMonthDayCell(
numberOfLeadingCells: daysFromPreviousMonth,
numberOfTrailingCells: remainingCells,
numberOfDaysInMonth: numberOfDaysInMonth
)
}
}

@ViewBuilder
private func previousEmptyCells(daysFromPreviousMonth: Int) -> some View {
let preEmptyCells = Array(0..<daysFromPreviousMonth)
.map {
DayCellIdentifiable(id: "pre-\($0)\(monthDate)", index: $0)
}
ForEach(preEmptyCells) { _ in
VStack(spacing: BPKSpacing.none) {
emptyLeadingDayCell()
.frame(height: dayCellHeight)
Spacer(minLength: BPKSpacing.none)
}
var emptyCellSpacing: BPKSpacing {
if let accessoryViewHeight, accessoryViewHeight > 0 {
return .sm
} else {
return .none
}
}

var totalCellHeight: CGFloat? {
guard let dayCellHeight, let accessoryViewHeight else { return nil }
return dayCellHeight + accessoryViewHeight + BPKSpacing.sm.value
}

@ViewBuilder
private func remainingEmptyCells(remainingCells: Int) -> some View {
if remainingCells < daysInAWeek {
let remainingEmptyCells = Array(0..<remainingCells)
.map {
DayCellIdentifiable(id: "rem-\($0)\(monthDate)", index: $0)
private func currentMonthDayCell(
numberOfLeadingCells: Int,
numberOfTrailingCells: Int,
numberOfDaysInMonth: Int
) -> some View {
// Create cells for the days in the current month
let days = Array(0..<numberOfDaysInMonth).map {
DayCellIdentifiable(id: "\(monthDate)\($0)", index: $0, type: .day)
}
// Create cells for the days from the previous month that are shown in the first week of the current month.
let leading = Array(0..<numberOfLeadingCells).map {
DayCellIdentifiable(id: "pre-\($0)", index: $0, type: .leadingEmpty)
}
// Create cells for the days from the next month that are shown in the last week of the current month
// The total number of cells used is the sum of the number of days in the current month and the number of
// days from the previous month that are shown
let trailing = Array(0..<numberOfTrailingCells).map {
DayCellIdentifiable(id: "rem-\($0)", index: $0, type: .trailingEmpty)
}
let total = leading + days + trailing

ForEach(total) { cell in
switch cell.type {
case .leadingEmpty:
VStack(spacing: emptyCellSpacing) {
emptyLeadingDayCell()
.frame(height: dayCellHeight)
Spacer(minLength: BPKSpacing.none)
.frame(height: accessoryViewHeight)
}
ForEach(remainingEmptyCells) { _ in
VStack(spacing: BPKSpacing.none) {
case .day:
dayCell(for: cell.index)
case .trailingEmpty:
VStack(spacing: emptyCellSpacing) {
emptyTrailingDayCell()
.frame(height: dayCellHeight)
Spacer(minLength: BPKSpacing.none)
.frame(height: accessoryViewHeight)
}
.frame(height: totalCellHeight)
}

}
}

private struct DayCellIdentifiable: Identifiable {
let id: String
let index: Int
}

@ViewBuilder
private func currentMonthDayCell(numberOfDaysInMonth: Int) -> some View {
let days = Array(0..<numberOfDaysInMonth)
.map {
DayCellIdentifiable(id: "\(monthDate)\($0)", index: $0)
@ViewBuilder private func dayCell(for index: Int) -> some View {
let dayDate = calendar.date(
byAdding: .init(day: index),
to: monthDate
)!

if !validRange.contains(dayDate) {
VStack(spacing: BPKSpacing.none) {
DisabledCalendarDayCell(calendar: calendar, date: dayDate)
.frame(height: dayCellHeight)
Spacer(minLength: BPKSpacing.none)
.frame(height: accessoryViewHeight)
}
ForEach(days) { cellIndex in
let dayDate = calendar.date(
byAdding: .init(day: cellIndex.index),
to: monthDate
)!

if !validRange.contains(dayDate) {
VStack(spacing: BPKSpacing.none) {
DisabledCalendarDayCell(calendar: calendar, date: dayDate)
.frame(height: dayCellHeight)
Spacer(minLength: BPKSpacing.none)
}
} else {
VStack(spacing: BPKSpacing.sm) {
dayCell(dayDate)
.modifier(ReadSizeModifier { dayCellHeight = $0.height })
dayAccessoryView(dayDate)
}
} else {
VStack(spacing: BPKSpacing.sm) {
dayCell(dayDate)
.modifier(ReadSizeModifier {
dayCellHeight = max($0.height, dayCellHeight ?? 0)
})
.frame(height: dayCellHeight)
dayAccessoryView(dayDate)
.modifier(ReadSizeModifier {
let height = CGFloat(ceil(Double($0.height)))
if accessoryViewHeight != height {
print("accessoryViewHeight", height)
accessoryViewHeight = max(height, accessoryViewHeight ?? 0)
}
})
.frame(height: accessoryViewHeight)
}
}
}
Expand Down
39 changes: 22 additions & 17 deletions Backpack-SwiftUI/Tests/Calendar/BPKCalendarTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,14 @@ class BPKCalendarTests: XCTestCase {
returnDatePrompt: ""
)

private var calendar: Calendar {
var calendar = Calendar(identifier: .gregorian)
calendar.locale = Locale(identifier: "en_US_POSIX")
return calendar
}

func test_singleSelectionCalendar() {
let testDate = Calendar.current.date(from: DateComponents(year: 2020, month: 2, day: 5))!
let testDate = calendar.date(from: DateComponents(year: 2020, month: 2, day: 5))!

assertSnapshot(
BPKCalendar(
Expand All @@ -45,58 +51,58 @@ class BPKCalendarTests: XCTestCase {
selectionHint: ""
)
),
calendar: Calendar.current,
calendar: calendar,
validRange: validStart...validEnd
)
.frame(width: 320, height: 720)
)
}

func test_rangeSelectionCalendar_sameMonth() {
let selectionStart = Calendar.current.date(from: DateComponents(year: 2020, month: 2, day: 5))!
let selectionEnd = Calendar.current.date(from: DateComponents(year: 2020, month: 2, day: 18))!
let selectionStart = calendar.date(from: DateComponents(year: 2020, month: 2, day: 5))!
let selectionEnd = calendar.date(from: DateComponents(year: 2020, month: 2, day: 18))!

assertSnapshot(
BPKCalendar(
selectionType: .range(
selection: .constant(.range(selectionStart...selectionEnd)),
accessibilityConfigurations: rangeAccessibilityConfig
),
calendar: Calendar.current,
calendar: calendar,
validRange: validStart...validEnd
)
.frame(width: 320, height: 720)
)
}

func test_rangeSelectionCalendar_differentMonth() {
let selectionStart = Calendar.current.date(from: DateComponents(year: 2020, month: 1, day: 28))!
let selectionEnd = Calendar.current.date(from: DateComponents(year: 2020, month: 2, day: 5))!
let selectionStart = calendar.date(from: DateComponents(year: 2020, month: 1, day: 28))!
let selectionEnd = calendar.date(from: DateComponents(year: 2020, month: 2, day: 5))!

assertSnapshot(
BPKCalendar(
selectionType: .range(
selection: .constant(.range(selectionStart...selectionEnd)),
accessibilityConfigurations: rangeAccessibilityConfig
),
calendar: Calendar.current,
calendar: calendar,
validRange: validStart...validEnd
)
.frame(width: 320, height: 720)
)
}

func test_rangeSelectionCalendar_withAccessoryView() {
let selectionStart = Calendar.current.date(from: DateComponents(year: 2020, month: 1, day: 28))!
let selectionEnd = Calendar.current.date(from: DateComponents(year: 2020, month: 2, day: 5))!
let selectionStart = calendar.date(from: DateComponents(year: 2020, month: 1, day: 28))!
let selectionEnd = calendar.date(from: DateComponents(year: 2020, month: 2, day: 5))!

assertSnapshot(
BPKCalendar(
selectionType: .range(
selection: .constant(.range(selectionStart...selectionEnd)),
accessibilityConfigurations: rangeAccessibilityConfig
),
calendar: Calendar.current,
calendar: calendar,
validRange: validStart...validEnd,
dayAccessoryView: { _ in
BPKIconView(.search, size: .small)
Expand All @@ -108,24 +114,23 @@ class BPKCalendarTests: XCTestCase {
}

func test_rangeSelectionCalendar_sameDay() {
let selectionStart = Calendar.current.date(from: DateComponents(year: 2020, month: 2, day: 5))!
let selectionEnd = Calendar.current.date(from: DateComponents(year: 2020, month: 2, day: 5))!
let selectionStart = calendar.date(from: DateComponents(year: 2020, month: 2, day: 5))!
let selectionEnd = calendar.date(from: DateComponents(year: 2020, month: 2, day: 5))!

assertSnapshot(
BPKCalendar(
selectionType: .range(
selection: .constant(.range(selectionStart...selectionEnd)),
accessibilityConfigurations: rangeAccessibilityConfig
),
calendar: Calendar.current,
calendar: calendar,
validRange: validStart...validEnd
)
.frame(width: 320, height: 720)
)
}

func test_rangeCalendarDayCells() {
let calendar = Calendar.current
let date = calendar.date(from: .init(year: 2023, month: 11, day: 8))!

assertSnapshot(
Expand All @@ -142,7 +147,7 @@ class BPKCalendarTests: XCTestCase {
}

func test_singleSelectionCalendarWithNoFloatYearLabel() {
let testDate = Calendar.current.date(from: DateComponents(year: 2020, month: 2, day: 5))!
let testDate = calendar.date(from: DateComponents(year: 2020, month: 2, day: 5))!

assertSnapshot(
BPKCalendar(
Expand All @@ -152,7 +157,7 @@ class BPKCalendarTests: XCTestCase {
selectionHint: ""
)
),
calendar: Calendar.current,
calendar: calendar,
validRange: validStart...validEnd,
showFloatYearLabel: false
)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading