diff --git a/Sources/Internal/Enums/MWeekday.swift b/Sources/Internal/Enums/MWeekday.swift new file mode 100644 index 0000000..851d62c --- /dev/null +++ b/Sources/Internal/Enums/MWeekday.swift @@ -0,0 +1,23 @@ +// +// MWeekday.swift of CalendarView +// +// Created by Alina Petrovska on 29.10.2023. +// - Mail: alina.petrovskaya@mijick.com +// - GitHub: https://github.com/Mijick +// +// Copyright ©2023 Mijick. Licensed under MIT License. + + +extension MWeekday { + static var allCases: [MWeekday] { + let firstDayIndex = MCalendar.firstWeekday.rawValue + let weekDaysIndexes = [Int](firstDayIndex ... 7) + [Int](1 ..< firstDayIndex) + + return .init(weekDaysIndexes) + } +} + +// MARK: - Helpers +fileprivate extension [MWeekday] { + init(_ indexes: [Int]) { self = indexes.compactMap { .init(rawValue: $0) }} +} diff --git a/Sources/Public/Configurables/Public+CalendarConfig.swift b/Sources/Public/Configurables/Public+CalendarConfig.swift index cad2e4d..e64afb5 100644 --- a/Sources/Public/Configurables/Public+CalendarConfig.swift +++ b/Sources/Public/Configurables/Public+CalendarConfig.swift @@ -13,37 +13,68 @@ import SwiftUI // MARK: - Calendar Configuration public extension CalendarConfig { + /// Sets the start date of the calendar. + /// DEFAULT: Current month func startMonth(_ value: Date) -> Self { MCalendar.startDate = value.start(of: .month); return self } + + /// Sets the end date of the calendar. + /// DEFAULT: A date in 10 years func endMonth(_ value: Date) -> Self { MCalendar.endDate = value.end(of: .month); return self } + + /// Sets the first day of the week. + /// DEFAULT:: Monday func firstWeekday(_ value: MWeekday) -> Self { MCalendar.firstWeekday = value; return self } + + /// Sets the locale of the calendar. + /// DEFAULT: AutoupdatingCurrent func locale(_ value: Locale) -> Self { MCalendar.locale = value; return self } } // MARK: - Distances Between Objects public extension CalendarConfig { + /// Sets the top scroll padding in the view. func monthsTopPadding(_ value: CGFloat) -> Self { changing(path: \.monthsPadding.top, to: value) } + + /// Sets the bottom scroll padding in the view. func monthsBottomPadding(_ value: CGFloat) -> Self { changing(path: \.monthsPadding.bottom, to: value) } + + /// Sets the distance between the month label and the day cell in the view. func monthLabelToDaysDistance(_ value: CGFloat) -> Self { changing(path: \.monthLabelDaysSpacing, to: value) } + + /// Sets the spacing between months in the view. func monthsSpacing(_ value: CGFloat) -> Self { changing(path: \.monthsSpacing, to: value) } + + /// Sets the vertical spacing between day cells in the view. func daysVerticalSpacing(_ value: CGFloat) -> Self { changing(path: \.daysSpacing.vertical, to: value) } + + /// Sets the horizontal spacing between day cells in the view. func daysHorizontalSpacing(_ value: CGFloat) -> Self { changing(path: \.daysSpacing.horizontal, to: value) } } -// MARK: - Custom Views +// MARK: - View Customisation public extension CalendarConfig { - func monthLabel(_ builder: @escaping (Date) -> some MonthLabel) -> Self { changing(path: \.monthLabel, to: builder) } - func weekdaysView(_ builder: @escaping () -> some WeekdaysView) -> Self { changing(path: \.weekdaysView, to: builder) } - func dayView(_ builder: @escaping (Date, Bool, Binding?, Binding?) -> some DayView) -> Self { changing(path: \.dayView, to: builder) } + /// Sets the background for the months view. + func monthsViewBackground(_ value: Color) -> Self { changing(path: \.monthsViewBackground, to: value) } } -// MARK: - View Customisation +// MARK: - Custom Views public extension CalendarConfig { - func monthsViewBackground(_ value: Color) -> Self { changing(path: \.monthsViewBackground, to: value) } + /// Replaces the default weekdays view with a selected implementation. + func weekdaysView(_ builder: @escaping () -> some WeekdaysView) -> Self { changing(path: \.weekdaysView, to: builder) } + + /// Replaces the default month label with a selected implementation. + func monthLabel(_ builder: @escaping (Date) -> some MonthLabel) -> Self { changing(path: \.monthLabel, to: builder) } + + /// Replaces the default day view with a selected implementation. + func dayView(_ builder: @escaping (Date, Bool, Binding?, Binding?) -> some DayView) -> Self { changing(path: \.dayView, to: builder) } } // MARK: - Modifiers public extension CalendarConfig { + /// Scrolls the calendar to the selected date. func scrollTo(date: Date?) -> Self { changing(path: \.scrollDate, to: date) } + + /// Triggers when a new month is about to be visible. func onMonthChange(_ value: @escaping (Date) -> ()) -> Self { changing(path: \.onMonthChange, to: value) } } @@ -57,8 +88,8 @@ public struct CalendarConfig: Configurable { public init() {} private(set) var monthsViewBackground: Color = .clear - private(set) var monthLabel: (Date) -> any MonthLabel = DefaultMonthLabel.init private(set) var weekdaysView: () -> any WeekdaysView = DefaultWeekdaysView.init + private(set) var monthLabel: (Date) -> any MonthLabel = DefaultMonthLabel.init private(set) var dayView: (Date, Bool, Binding?, Binding?) -> any DayView = DefaultDayView.init private(set) var scrollDate: Date? = nil diff --git a/Sources/Public/Enums/Public+MWeekday.swift b/Sources/Public/Enums/Public+MWeekday.swift index 3eded44..eb07494 100644 --- a/Sources/Public/Enums/Public+MWeekday.swift +++ b/Sources/Public/Enums/Public+MWeekday.swift @@ -8,19 +8,4 @@ // Copyright ©2023 Mijick. Licensed under MIT License. -import Foundation - public enum MWeekday: Int { case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday } -extension MWeekday { - static var allCases: [MWeekday] { - let firstDayIndex = MCalendar.firstWeekday.rawValue - let weekDaysIndexes = [Int](firstDayIndex ... 7) + [Int](1 ..< firstDayIndex) - - return .init(weekDaysIndexes) - } -} - -// MARK: - Helpers -fileprivate extension [MWeekday] { - init(_ indexes: [Int]) { self = indexes.compactMap { .init(rawValue: $0) }} -} diff --git a/Sources/Public/Extensions/Public+MCalendarView.swift b/Sources/Public/Extensions/Public+MCalendarView.swift index 2636e80..a02930a 100644 --- a/Sources/Public/Extensions/Public+MCalendarView.swift +++ b/Sources/Public/Extensions/Public+MCalendarView.swift @@ -12,7 +12,5 @@ import SwiftUI extension MCalendarView { - public init(selectedDate: Binding?, selectedRange: Binding?, configBuilder: (CalendarConfig) -> CalendarConfig = { $0 }) { - self.init(selectedDate, selectedRange, configBuilder) - } + public init(selectedDate: Binding?, selectedRange: Binding?, configBuilder: (CalendarConfig) -> CalendarConfig = { $0 }) { self.init(selectedDate, selectedRange, configBuilder) } } diff --git a/Sources/Public/View Protocols/Public+DayView.swift b/Sources/Public/View Protocols/Public+DayView.swift index 84e4f21..d7c080e 100644 --- a/Sources/Public/View Protocols/Public+DayView.swift +++ b/Sources/Public/View Protocols/Public+DayView.swift @@ -29,22 +29,14 @@ public protocol DayView: View { func onSelection() } -// MARK: - Customising View +// MARK: - Default View Implementation public extension DayView { func createContent() -> AnyView { createDefaultContent().erased() } func createDayLabel() -> AnyView { createDefaultDayLabel().erased() } func createSelectionView() -> AnyView { createDefaultSelectionView().erased() } func createRangeSelectionView() -> AnyView { createDefaultRangeSelectionView().erased() } - - var body: some View { createBody() } } private extension DayView { - func createBody() -> some View { - Group { - if isCurrentMonth { createBodyForCurrentMonth() } - else { createBodyForOtherMonth() } - } - } func createDefaultContent() -> some View { ZStack { createSelectionView() createRangeSelectionView() @@ -68,16 +60,6 @@ private extension DayView { .active(if: isWithinRange()) } } -private extension DayView { - func createBodyForCurrentMonth() -> some View { - createContent() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .aspectRatio(1.0, contentMode: .fit) - .onAppear(perform: onAppear) - .onTapGesture(perform: onSelection) - } - func createBodyForOtherMonth() -> some View { Rectangle().fill(Color.clear) } -} private extension DayView { var rangeSelectionViewCorners: RoundedRectangle.Corner { if isBeginningOfRange() { return [.topLeft, .bottomLeft] } @@ -87,31 +69,52 @@ private extension DayView { } } -// MARK: - Handling Actions +// MARK: - Default Logic Implementation public extension DayView { func onAppear() {} func onSelection() { selectedDate?.wrappedValue = date } } -// MARK: - Text Formatting +// MARK: - Helpers + +// MARK: Text Formatting public extension DayView { + /// Returns a string of the selected format for the date. func getStringFromDay(format: String) -> String { MDateFormatter.getString(from: date, format: format) } } -// MARK: - Date Helpers +// MARK: Date Helpers public extension DayView { func isPast() -> Bool { date.isBefore(.day, than: .now) } func isToday() -> Bool { date.isSame(.day, as: .now) } } -// MARK: - Day Selection Helpers +// MARK: Day Selection Helpers public extension DayView { func isSelected() -> Bool { date.isSame(.day, as: selectedDate?.wrappedValue) || isBeginningOfRange() || isEndOfRange() } } -// MARK: - Range Selection Helpers +// MARK: Range Selection Helpers public extension DayView { func isBeginningOfRange() -> Bool { date.isSame(.day, as: selectedRange?.wrappedValue?.getRange()?.lowerBound) } func isEndOfRange() -> Bool { date.isSame(.day, as: selectedRange?.wrappedValue?.getRange()?.upperBound) } func isWithinRange() -> Bool { selectedRange?.wrappedValue?.isRangeCompleted() == true && selectedRange?.wrappedValue?.contains(date) == true } } + +// MARK: - Others +public extension DayView { + var body: some View { Group { + if isCurrentMonth { createBodyForCurrentMonth() } + else { createBodyForOtherMonth() } + }} +} +private extension DayView { + func createBodyForCurrentMonth() -> some View { + createContent() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .aspectRatio(1.0, contentMode: .fit) + .onAppear(perform: onAppear) + .onTapGesture(perform: onSelection) + } + func createBodyForOtherMonth() -> some View { Rectangle().fill(Color.clear) } +} diff --git a/Sources/Public/View Protocols/Public+MonthLabel.swift b/Sources/Public/View Protocols/Public+MonthLabel.swift index 0acac54..c8019d8 100644 --- a/Sources/Public/View Protocols/Public+MonthLabel.swift +++ b/Sources/Public/View Protocols/Public+MonthLabel.swift @@ -11,15 +11,16 @@ import SwiftUI public protocol MonthLabel: View { + // MARK: Required Attributes var month: Date { get } + // MARK: View Customisation func createContent() -> AnyView } -// MARK: - Customizing View +// MARK: - Default View Implementation public extension MonthLabel { func createContent() -> AnyView { createDefaultContent().erased() } - var body: some View { createContent() } } private extension MonthLabel { func createDefaultContent() -> some View { @@ -31,5 +32,11 @@ private extension MonthLabel { // MARK: - Helpers public extension MonthLabel { + /// Returns a string of the selected format for the month. func getString(format: String) -> String { MDateFormatter.getString(from: month, format: format) } } + +// MARK: - Others +public extension MonthLabel { + var body: some View { createContent() } +} diff --git a/Sources/Public/View Protocols/Public+WeekdayLabel.swift b/Sources/Public/View Protocols/Public+WeekdayLabel.swift index 25070c5..7e793b1 100644 --- a/Sources/Public/View Protocols/Public+WeekdayLabel.swift +++ b/Sources/Public/View Protocols/Public+WeekdayLabel.swift @@ -11,15 +11,16 @@ import SwiftUI public protocol WeekdayLabel: View { + // MARK: Required Attributes var weekday: MWeekday { get } + // MARK: View Customisation func createContent() -> AnyView } -// MARK: - Customising View +// MARK: - Default View Implementation public extension WeekdayLabel { func createContent() -> AnyView { createDefaultContent().erased() } - var body: some View { createContent() } } private extension WeekdayLabel { func createDefaultContent() -> some View { @@ -31,8 +32,14 @@ private extension WeekdayLabel { // MARK: - Helpers public extension WeekdayLabel { + /// Returns a string of the selected format for the weekday. + func getString(with format: WeekdaySymbolFormat) -> String { MDateFormatter.getString(for: weekday, format: format) } + + /// Returns a type-erased object. func erased() -> AnyWeekdayLabel { .init(self) } } + +// MARK: - Others public extension WeekdayLabel { - func getString(with format: WeekdaySymbolFormat) -> String { MDateFormatter.getString(for: weekday, format: format) } + var body: some View { createContent() } } diff --git a/Sources/Public/View Protocols/Public+WeekdaysView.swift b/Sources/Public/View Protocols/Public+WeekdaysView.swift index a1ac9e2..81b3f59 100644 --- a/Sources/Public/View Protocols/Public+WeekdaysView.swift +++ b/Sources/Public/View Protocols/Public+WeekdaysView.swift @@ -10,29 +10,31 @@ import SwiftUI -public protocol WeekdaysView: View { +public protocol WeekdaysView: View { + // MARK: View Customisation func createContent() -> AnyView func createWeekdayLabel(_ weekday: MWeekday) -> AnyWeekdayLabel } -// MARK: - Customising View +// MARK: - Default View Implementation public extension WeekdaysView { - func createContent() -> AnyView { createDefaultContent().erased() } - func createWeekdaysView() -> AnyView { createDefaultWeekdaysView().erased() } + func createContent() -> AnyView { createWeekdaysView().erased() } func createWeekdayLabel(_ weekday: MWeekday) -> AnyWeekdayLabel { createDefaultWeekDayLabel(weekday).erased() } - - var body: some View { createContent() } } private extension WeekdaysView { - func createDefaultContent() -> some View { createWeekdaysView() } - func createDefaultWeekdaysView() -> some View { HStack(spacing: 0) { ForEach(weekdays, id: \.self, content: createWeekdayItem) }} func createDefaultWeekDayLabel(_ weekday: MWeekday) -> DefaultWeekdayLabel { DefaultWeekdayLabel(weekday: weekday) } } + +// MARK: - Helpers +public extension WeekdaysView { + /// Creates weekdays view using the selected weekday labels. Cannot be overriden. + func createWeekdaysView() -> some View { HStack(spacing: 0) { ForEach(MWeekday.allCases, id: \.self, content: createWeekdayItem) } } +} private extension WeekdaysView { func createWeekdayItem(_ weekday: MWeekday) -> some View { createWeekdayLabel(weekday).frame(maxWidth: .infinity) } } -// MARK: Helpers +// MARK: - Others public extension WeekdaysView { - var weekdays: [MWeekday] { MWeekday.allCases } + var body: some View { createContent() } }