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

[LMN-20] SwiftUI Calendar component #1818

Merged
merged 22 commits into from
Nov 22, 2023
Merged

[LMN-20] SwiftUI Calendar component #1818

merged 22 commits into from
Nov 22, 2023

Conversation

frugoman
Copy link
Contributor

@frugoman frugoman commented Nov 14, 2023

Initial implementation for the Calendar component purely on SwiftUI

Following: Figma's Calendar

currently supporting:

  • Single selection
  • Range selection
  • Adding accessory action for each month

image
image
image

Remaining features to match UIKit's implementation will be added on a needed-basis, like:

  • Multiple selection
  • Select whole month accessory action (working within the component)
  • Support for adding additional info on each day in calendar (like prices)

unnecessary elements in Calendar components
@frugoman frugoman marked this pull request as ready for review November 15, 2023 15:52
SingleCalendarContainer_Previews
self.selectionType = selectionType

monthHeaderDateFormatter = DateFormatter()
monthHeaderDateFormatter.dateFormat = "MMMM yyyy"

Choose a reason for hiding this comment

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

Does this date format work for RTL and LTR languages?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's the same format we used before! So yeah it's the way we expect it to be!

/// is selected as both the first and second date in the range.
/// - returnDatePrompt: The prompt provided to assistive technologies informing a user that they should now
/// select a second date.
public struct RangeAccessibilityConfigurations {

Choose a reason for hiding this comment

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

Should this struct live within the calendar component or be a general Backpack utility? Just asking about which folder this should live in for discoverability purposes. If it's in here, there's less chance of it being re-used elsewhere

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's something that's specific for the Calendar, so living within the Calendar folder is the right place I think. It should not be reused, it's super specific to this!

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

assertSnapshot(

Choose a reason for hiding this comment

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

👍 very useful to have all the states tested, and visualised through the snapshot 😄


import SwiftUI

struct CalendarBadge: View {

Choose a reason for hiding this comment

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

Is it worth having snapshot tests for this as well?

Choose a reason for hiding this comment

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

Unless I missed these...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This already appears in the calendar snapshot, I don't think its worth it to have it's own, given also how simple it is!


/// CalendarMonthHeader is a view that displays the month name and an optional accessory action.
/// Also exposes a binding to the currently shown month.
struct CalendarMonthHeader: View {

Choose a reason for hiding this comment

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

Should this have a snapshot test as well, it looks like it contains some layout logic for different states

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's also covered by the other snapshots! I dont think it needs its own!

@ViewBuilder let monthContent: (_ month: Date) -> MonthContent

var body: some View {
ScrollView {

Choose a reason for hiding this comment

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

From what I can tell, you're not doing any cell recycling of the months, I guess that's because there's a limit of 12 months, so it's not necessary, is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the months themselves are not recycled, but the day cells are. if both the day cells and months are dynamically resolved while scrolling, that produces jumps in the UI while scrolling, because a new month will load but then more cells need to render (the days), pushing content up more.

Later on, when we can support using the Grid component with iOS 16 (only LazyVGrid is available for ios 14 :/ ) we'll be able to switch to recycling months and not the days, which would be preferred!

So we're kinda cornered here by the ios versions we support!

private let daysInAWeek = 7

var body: some View {
// Sunday is the first day of the week, so we need to offset the days to make Monday the first day

Choose a reason for hiding this comment

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

I think you don't need the 1st line of this comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ahh good catch!

Comment on lines 29 to 35
LowerAndUpperBoundSelectedCell(calendar: calendar, date: date)
} else if date == selection.lowerBound {
LowerBoundSelectedCell(calendar: calendar, date: date)
} else if date == selection.upperBound {
UpperBoundSelectedCell(calendar: calendar, date: date)
} else {
InbetweenSelectionCell(calendar: calendar, date: date)

Choose a reason for hiding this comment

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

I'm trying to consider the pros / cons of making individual cells like this versus have a view modifier to apply these various states to a common calendar cell.
I guess the way you have it here with distinct View structs gives some simplicity, and clarity when reading this, but it implies more duplication, eg we have to create a BPKText() view in each cell, and apply mostly the same styling to it each time.
Just wondering if you considered making view modifiers to represent each of these cell types, which could be applied to a common view, or if you think that would end up being too painful / not working?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I considered it yes. I ended up with different views for each day cell type to make them not depend on each other too much and have more flexibility when adding more cell types (for example, the component needs to support (not in this PR) adding prices to the day cells)

So flexibility here is more worth having than some duplication.

Duplication is not really happening much though here (except for maybe the fact it's using a BPKText with with lineLimit=1) and a modifier would be slightly harder to use for this usecase, in general, conditionally adding modifiers should not happen, as re-composing should be the approach taken... aaaaanyways haha the different structs approach is prefered for this case

Copy link
Contributor

@gert-janvercauteren gert-janvercauteren left a comment

Choose a reason for hiding this comment

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

I noticed that the day headings use a different format compared to the existing calendar. Should we align Mon / Tue / Wed between both UIKit and SwiftUI?

There's also a bit of funkyness when a selection happens, which leads to a double read out of a cell, it feels like it redraws a large portion of the view when the state changes.

@frugoman
Copy link
Contributor Author

I noticed that the day headings use a different format compared to the existing calendar. Should we align Mon / Tue / Wed between both UIKit and SwiftUI?

There's also a bit of funkyness when a selection happens, which leads to a double read out of a cell, it feels like it redraws a large portion of the view when the state changes.

About showing the months Mon/Tue/Wed or M/T/W, I followed Figma for this, which shows days using only one letter!

on the selection funkyness with VoiceOver, I'm not really sure what's going on? I've debugged every component taking part here and nothing I could find gets re-rendered.

@frugoman frugoman requested a review from MBaldo83 November 20, 2023 18:04
@frugoman frugoman enabled auto-merge (squash) November 21, 2023 12:37
@frugoman frugoman merged commit fd00953 into main Nov 22, 2023
@frugoman frugoman deleted the swiftui-calendar branch November 22, 2023 09:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants