Skip to content

Commit

Permalink
Bk/rename day and month (#287)
Browse files Browse the repository at this point in the history
* Suffix Day and Month with Components

* Update example project

* Update documentation
  • Loading branch information
bryankeller authored Dec 19, 2023
1 parent 66dd26d commit 4332559
Show file tree
Hide file tree
Showing 14 changed files with 84 additions and 62 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated content-change animations so that the same scroll offset is maintained throughout the animation
- Changed the Swift version needed to use HorizonCalendar to 5.8
- Simplified accessibility (Voice Over) support so that it works consistently for calendars containing UIKit and SwiftUI views
- Renamed `Day` to `DayComponents` and `Month` to `MonthComponents` to clarify their intended use


## [v1.16.0](https://github.com/airbnb/HorizonCalendar/compare/v1.15.0...v1.16.0) - 2023-01-30
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import UIKit

enum DayRangeSelectionHelper {

static func updateDayRange(afterTapSelectionOf day: Day, existingDayRange: inout DayRange?) {
static func updateDayRange(
afterTapSelectionOf day: DayComponents,
existingDayRange: inout DayComponentsRange?)
{
if
let _existingDayRange = existingDayRange,
_existingDayRange.lowerBound == _existingDayRange.upperBound,
Expand All @@ -31,9 +34,9 @@ enum DayRangeSelectionHelper {
}

static func updateDayRange(
afterDragSelectionOf day: Day,
existingDayRange: inout DayRange?,
initialDayRange: inout DayRange?,
afterDragSelectionOf day: DayComponents,
existingDayRange: inout DayComponentsRange?,
initialDayRange: inout DayComponentsRange?,
state: UIGestureRecognizer.State,
calendar: Calendar)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ final class DayRangeSelectionDemoViewController: BaseDemoViewController {

// MARK: Private

private var selectedDayRange: DayRange?
private var selectedDayRangeAtStartOfDrag: DayRange?
private var selectedDayRange: DayComponentsRange?
private var selectedDayRangeAtStartOfDrag: DayComponentsRange?

}
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ struct SwiftUIScreenDemo: View {

@StateObject private var calendarViewProxy = CalendarViewProxy()

@State private var selectedDayRange: DayRange?
@State private var selectedDayRangeAtStartOfDrag: DayRange?
@State private var selectedDayRange: DayComponentsRange?
@State private var selectedDayRangeAtStartOfDrag: DayComponentsRange?

private var selectedDateRanges: Set<ClosedRange<Date>> {
guard let selectedDayRange else { return [] }
Expand All @@ -189,7 +189,7 @@ struct SwiftUIScreenDemo: View {
return [selectedStartDate...selectedEndDate]
}

private func isDaySelected(_ day: Day) -> Bool {
private func isDaySelected(_ day: DayComponents) -> Bool {
if let selectedDayRange {
return day == selectedDayRange.lowerBound || day == selectedDayRange.upperBound
} else {
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ private func makeContent() -> CalendarViewContent {
}
```

The `dayItemProvider(_:)` function on `CalendarViewContent` returns a new `CalendarViewContent` instance with the custom day item model provider configured. This function takes a single parameter - a provider closure that returns a `CalendarItemModel` for a given `Day`.
The `dayItemProvider(_:)` function on `CalendarViewContent` returns a new `CalendarViewContent` instance with the custom day item model provider configured. This function takes a single parameter - a provider closure that returns a `CalendarItemModel` for a given `DayComponents`.

`CalendarItemModel` is a type that abstracts away the creation and configuration of a view displayed in the calendar. It's generic over a `ViewRepresentable` type, which can be any type conforming to `CalendarItemViewRepresentable`. You can think of `CalendarItemViewRepresentable` as a blueprint for creating and updating instances of a particular type of view to be displayed in the calendar. For example, if we want to use a `UILabel` for our custom day view, we'll need to create a type that knows how to create and update that label. Here's a simple example:
```swift
Expand All @@ -213,7 +213,7 @@ struct DayLabel: CalendarItemViewRepresentable {

/// Properties that will vary depending on the particular date being displayed.
struct Content: Equatable {
let day: Day
let day: DayComponents
}

static func makeView(
Expand Down Expand Up @@ -298,7 +298,7 @@ After building and running your app, you should see a much less cramped layout:
#### Adding a day range indicator
Day range indicators are useful for date pickers that need to highlight not just individual days, but ranges of days. `HorizonCalendar` offers an API to do exactly this via the `CalendarViewContent` function `dayRangeItemProvider(for:_:)`. Similar to what we did for our custom day item model provider, for day ranges, we need to provide a `CalendarItemModel` for each day range we want to highlight.

First, we need to create a `ClosedRange<Date>` that represents the day range for which we'd like to provide a `CalendarItemModel`. The `Date`s in our range will be interpreted as `Day`s using the `Calendar` instance with which we initialized our `CalendarViewContent`.
First, we need to create a `ClosedRange<Date>` that represents the day range for which we'd like to provide a `CalendarItemModel`. The `Date`s in our range will be interpreted as `DayComponents`s using the `Calendar` instance with which we initialized our `CalendarViewContent`.
```swift
let lowerDate = calendar.date(from: DateComponents(year: 2020, month: 01, day: 20))!
let upperDate = calendar.date(from: DateComponents(year: 2020, month: 02, day: 07))!
Expand Down Expand Up @@ -609,10 +609,10 @@ calendarView.daySelectionHandler = { [weak self] day in
```

```swift
private var selectedDay: Day?
private var selectedDay: DayComponents?
```

The day selection handler closure is invoked whenever a day in the calendar is selected. You're provided with a `Day` instance for the day that was selected. If we want to highlight the selected day once its been tapped, we'll need to create a new `CalendarViewContent` with a day calendar item model that looks different for the selected day:
The day selection handler closure is invoked whenever a day in the calendar is selected. You're provided with a `DayComponents` instance for the day that was selected. If we want to highlight the selected day once its been tapped, we'll need to create a new `CalendarViewContent` with a day calendar item model that looks different for the selected day:
```swift
let selectedDay = self.selectedDay

Expand Down
14 changes: 7 additions & 7 deletions Sources/Public/CalendarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,22 @@ public final class CalendarView: UIView {
/// A closure (that is retained) that is invoked whenever a day is selected. It is the responsibility of your feature code to decide what to
/// do with each day. For example, you might store the most recent day in a selected day property, then read that property in your
/// `dayItemProvider` closure to add specific "selected" styling to a particular day view.
public var daySelectionHandler: ((Day) -> Void)?
public var daySelectionHandler: ((DayComponents) -> Void)?

/// A closure (that is retained) that is invoked inside `scrollViewDidScroll(_:)`
public var didScroll: ((_ visibleDayRange: DayRange, _ isUserDragging: Bool) -> Void)?
public var didScroll: ((_ visibleDayRange: DayComponentsRange, _ isUserDragging: Bool) -> Void)?

/// A closure (that is retained) that is invoked inside `scrollViewDidEndDragging(_: willDecelerate:)`.
public var didEndDragging: ((_ visibleDayRange: DayRange, _ willDecelerate: Bool) -> Void)?
public var didEndDragging: ((_ visibleDayRange: DayComponentsRange, _ willDecelerate: Bool) -> Void)?

/// A closure (that is retained) that is invoked inside `scrollViewDidEndDecelerating(_:)`.
public var didEndDecelerating: ((_ visibleDayRange: DayRange) -> Void)?
public var didEndDecelerating: ((_ visibleDayRange: DayComponentsRange) -> Void)?

/// A closure (that is retained) that is invoked during a multiple-selection-drag-gesture. Multiple selection is initiated with a long press,
/// followed by a drag / pan. As the gesture crosses over more days in the calendar, this handler will be invoked with each new day. It
/// is the responsibility of your feature code to decide what to do with this stream of days. For example, you might convert them to
/// `Date` instances and use them as input to the `dayRangeItemProvider`.
public var multiDaySelectionDragHandler: ((Day, UIGestureRecognizer.State) -> Void)? {
public var multiDaySelectionDragHandler: ((DayComponents, UIGestureRecognizer.State) -> Void)? {
didSet {
configureMultiDaySelectionPanGestureRecognizer()
}
Expand All @@ -94,12 +94,12 @@ public final class CalendarView: UIView {
}

/// The range of months that are partially of fully visible.
public var visibleMonthRange: MonthRange? {
public var visibleMonthRange: MonthComponentsRange? {
visibleItemsDetails?.visibleMonthRange
}

/// The range of days that are partially or fully visible.
public var visibleDayRange: DayRange? {
public var visibleDayRange: DayComponentsRange? {
visibleItemsDetails?.visibleDayRange
}

Expand Down
8 changes: 4 additions & 4 deletions Sources/Public/CalendarViewContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ public final class CalendarViewContent {
/// - month: The `Month` for which to provide a month header item.
/// - Returns: A mutated `CalendarViewContent` instance with a new month header item provider.
public func monthHeaderItemProvider(
_ monthHeaderItemProvider: @escaping (_ month: Month) -> AnyCalendarItemModel?)
_ monthHeaderItemProvider: @escaping (_ month: MonthComponents) -> AnyCalendarItemModel?)
-> CalendarViewContent
{
self.monthHeaderItemProvider = { [defaultMonthHeaderItemProvider] month in
Expand Down Expand Up @@ -217,7 +217,7 @@ public final class CalendarViewContent {
/// - Returns: A mutated `CalendarViewContent` instance with a new day-of-week item provider.
public func dayOfWeekItemProvider(
_ dayOfWeekItemProvider: @escaping (
_ month: Month?,
_ month: MonthComponents?,
_ weekdayIndex: Int)
-> AnyCalendarItemModel?)
-> CalendarViewContent
Expand Down Expand Up @@ -250,7 +250,7 @@ public final class CalendarViewContent {
/// - day: The `Day` for which to provide a day item.
/// - Returns: A mutated `CalendarViewContent` instance with a new day item provider.
public func dayItemProvider(
_ dayItemProvider: @escaping (_ day: Day) -> AnyCalendarItemModel?)
_ dayItemProvider: @escaping (_ day: DayComponents) -> AnyCalendarItemModel?)
-> CalendarViewContent
{
self.dayItemProvider = { [defaultDayItemProvider] day in
Expand Down Expand Up @@ -279,7 +279,7 @@ public final class CalendarViewContent {
/// - day: The `Day` for which to provide a day background item.
/// - Returns: A mutated `CalendarViewContent` instance with a new day background item provider.
public func dayBackgroundItemProvider(
_ dayBackgroundItemProvider: @escaping (_ day: Day) -> AnyCalendarItemModel?)
_ dayBackgroundItemProvider: @escaping (_ day: DayComponents) -> AnyCalendarItemModel?)
-> CalendarViewContent
{
self.dayBackgroundItemProvider = dayBackgroundItemProvider
Expand Down
30 changes: 15 additions & 15 deletions Sources/Public/CalendarViewRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ extension CalendarViewRepresentable {
/// - month: The `Month` for which to provide a month header item.
/// - Returns: A new `CalendarViewRepresentable` with a new month header item provider.
public func monthHeaderItemProvider(
_ monthHeaderItemProvider: @escaping (_ month: Month) -> AnyCalendarItemModel?)
_ monthHeaderItemProvider: @escaping (_ month: MonthComponents) -> AnyCalendarItemModel?)
-> Self
{
var view = self
Expand All @@ -350,7 +350,7 @@ extension CalendarViewRepresentable {
/// - month: The `Month` for which to provide a month header view.
/// - Returns: A new `CalendarViewRepresentable` with custom month header views configured.
public func monthHeaders(
@ViewBuilder _ content: @escaping (_ month: Month) -> some View)
@ViewBuilder _ content: @escaping (_ month: MonthComponents) -> some View)
-> CalendarViewRepresentable
{
monthHeaderItemProvider { month in
Expand All @@ -377,7 +377,7 @@ extension CalendarViewRepresentable {
/// - Returns: A new `CalendarViewRepresentable` with a new day-of-week item provider.
public func dayOfWeekItemProvider(
_ dayOfWeekItemProvider: @escaping (
_ month: Month?,
_ month: MonthComponents?,
_ weekdayIndex: Int)
-> AnyCalendarItemModel?)
-> Self
Expand All @@ -401,7 +401,7 @@ extension CalendarViewRepresentable {
/// - weekdayIndex: The weekday index for which to provide a day-of-week header view.
/// - Returns: A new `CalendarViewRepresentable` with custom day-of-week header views configured.
public func dayOfWeekHeaders(
_ content: @escaping (_ month: Month?, _ weekdayIndex: Int) -> some View)
_ content: @escaping (_ month: MonthComponents?, _ weekdayIndex: Int) -> some View)
-> Self
{
dayOfWeekItemProvider { month, weekdayIndex in
Expand All @@ -425,7 +425,7 @@ extension CalendarViewRepresentable {
/// - day: The `Day` for which to provide a day item.
/// - Returns: A new `CalendarViewRepresentable` with a new day item provider.
public func dayItemProvider(
_ dayItemProvider: @escaping (_ day: Day) -> AnyCalendarItemModel?)
_ dayItemProvider: @escaping (_ day: DayComponents) -> AnyCalendarItemModel?)
-> Self
{
var view = self
Expand All @@ -445,7 +445,7 @@ extension CalendarViewRepresentable {
/// - day: The `Day` for which to provide a day view.
/// - Returns: A new `CalendarViewRepresentable` with custom day views configured.
public func days(
@ViewBuilder _ content: @escaping (_ day: Day) -> some View)
@ViewBuilder _ content: @escaping (_ day: DayComponents) -> some View)
-> Self
{
dayItemProvider { day in
Expand All @@ -468,7 +468,7 @@ extension CalendarViewRepresentable {
/// - day: The `Day` for which to provide a day background item.
/// - Returns: A new `CalendarViewRepresentable` with a new day background item provider.
public func dayBackgroundItemProvider(
_ dayBackgroundItemProvider: @escaping (_ day: Day) -> AnyCalendarItemModel?)
_ dayBackgroundItemProvider: @escaping (_ day: DayComponents) -> AnyCalendarItemModel?)
-> Self
{
var view = self
Expand All @@ -488,7 +488,7 @@ extension CalendarViewRepresentable {
/// - day: The `Day` for which to provide a day background view.
/// - Returns: A new `CalendarViewRepresentable` with day background views configured.
public func dayBackgrounds(
@ViewBuilder _ content: @escaping (_ day: Day) -> some View)
@ViewBuilder _ content: @escaping (_ day: DayComponents) -> some View)
-> Self
{
dayBackgroundItemProvider { day in
Expand Down Expand Up @@ -685,7 +685,7 @@ extension CalendarViewRepresentable {
///
/// - Parameters:
/// - daySelectionHandler: A closure (that is retained) that is invoked whenever a day is selected.
public func onDaySelection(_ daySelectionHandler: @escaping (Day) -> Void) -> Self {
public func onDaySelection(_ daySelectionHandler: @escaping (DayComponents) -> Void) -> Self {
var view = self
view.daySelectionHandler = daySelectionHandler
return view
Expand All @@ -704,9 +704,9 @@ extension CalendarViewRepresentable {
/// - changed: A closure (that is retained) that is invoked when the multiple-day-selection drag gesture intersects a new day.
/// - ended: A closure (that is retained) that is invoked when the multiple-day-selection drag gesture ends.
public func onMultipleDaySelectionDrag(
began: @escaping (Day) -> Void,
changed: @escaping (Day) -> Void,
ended: @escaping (Day) -> Void)
began: @escaping (DayComponents) -> Void,
changed: @escaping (DayComponents) -> Void,
ended: @escaping (DayComponents) -> Void)
-> Self
{
var view = self
Expand All @@ -726,7 +726,7 @@ extension CalendarViewRepresentable {
}

public func onScroll(
_ scrollHandler: @escaping (_ visibleDayRange: DayRange, _ isUserDragging: Bool) -> Void)
_ scrollHandler: @escaping (_ visibleDayRange: DayComponentsRange, _ isUserDragging: Bool) -> Void)
-> Self
{
var view = self
Expand All @@ -735,7 +735,7 @@ extension CalendarViewRepresentable {
}

public func onDragEnd(
_ dragEndHandler: @escaping (_ visibleDayRange: DayRange, _ willDecelerate: Bool) -> Void)
_ dragEndHandler: @escaping (_ visibleDayRange: DayComponentsRange, _ willDecelerate: Bool) -> Void)
-> Self
{
var view = self
Expand All @@ -744,7 +744,7 @@ extension CalendarViewRepresentable {
}

public func onDeceleratingEnd(
_ deceleratingEndHandler: @escaping (_ visibleDayRange: DayRange) -> Void)
_ deceleratingEndHandler: @escaping (_ visibleDayRange: DayComponentsRange) -> Void)
-> Self
{
var view = self
Expand Down
21 changes: 13 additions & 8 deletions Sources/Public/Day.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,25 @@ import Foundation

// MARK: - Day

/// Represents the components of a day (with a corresponding year and month) in a particular calendar. All days are assumed to have
/// been instantiated with the same `Calendar`, which is enforced throughout the implementation.
public struct Day: Hashable {
typealias Day = DayComponents

// MARK: - DayComponents

/// Represents the components of a day. This type is created internally, then vended to you via the public API. All `DayComponents`
/// instances that are vended to you are created using the `Calendar` instance that you provide when initializing your
/// `CalendarView`.
public struct DayComponents: Hashable {

// MARK: Lifecycle

init(month: Month, day: Int) {
init(month: MonthComponents, day: Int) {
self.month = month
self.day = day
}

// MARK: Public

public let month: Month
public let month: MonthComponents
public let day: Int

public var components: DateComponents {
Expand All @@ -41,7 +46,7 @@ public struct Day: Hashable {

// MARK: CustomStringConvertible

extension Day: CustomStringConvertible {
extension DayComponents: CustomStringConvertible {

public var description: String {
let yearDescription = String(format: "%04d", month.year)
Expand All @@ -54,9 +59,9 @@ extension Day: CustomStringConvertible {

// MARK: Comparable

extension Day: Comparable {
extension DayComponents: Comparable {

public static func < (lhs: Day, rhs: Day) -> Bool {
public static func < (lhs: DayComponents, rhs: DayComponents) -> Bool {
guard lhs.month == rhs.month else { return lhs.month < rhs.month }
return lhs.day < rhs.day
}
Expand Down
10 changes: 7 additions & 3 deletions Sources/Public/DayRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@

import Foundation

// MARK: - DayRange
// MARK: DayRange

public typealias DayRange = ClosedRange<Day>
typealias DayRange = DayComponentsRange

extension DayRange {
// MARK: - DayComponentsRange

public typealias DayComponentsRange = ClosedRange<DayComponents>

extension DayComponentsRange {

/// Instantiates a `DayRange` that encapsulates the `dateRange` in the `calendar` as closely as possible. For example,
/// a date range of [2020-05-20T23:59:59, 2021-01-01T00:00:00] will result in a day range of [2020-05-20, 2021-01-01].
Expand Down
4 changes: 2 additions & 2 deletions Sources/Public/DayRangeLayoutContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ import CoreGraphics
/// of those frames. This can be used in a custom day range view to draw the day range in the correct location.
public struct DayRangeLayoutContext: Hashable {
/// The day range that this layout context describes.
public let dayRange: DayRange
public let dayRange: DayComponentsRange

/// An ordered list of tuples containing day and day frame pairs.
///
/// Each frame represents the frame of an individual day in the day range in the coordinate system of
/// `boundingUnionRectOfDayFrames`. If a day range extends beyond the `visibleDateRange`, this array will only
/// contain the day-frame pairs for the visible portion of the day range.
public let daysAndFrames: [(day: Day, frame: CGRect)]
public let daysAndFrames: [(day: DayComponents, frame: CGRect)]

/// A rectangle that perfectly contains all day frames in `daysAndFrames`. In other words, it is the union of all day frames in
/// `daysAndFrames`.
Expand Down
Loading

0 comments on commit 4332559

Please sign in to comment.