From ace3ae3f704dc95defe9d8d9838941e2fcf08660 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Wed, 3 Jan 2024 00:19:51 +0300 Subject: [PATCH 01/21] Add Swipe to change Month --- .../FXDatePickerDemo/ContentView.swift | 36 +++--- Sources/FXDatePicker/Views/DayView.swift | 6 +- .../FXDatePicker/Views/FXDatePickerView.swift | 106 +++++++++++++++--- Sources/FXDatePicker/Views/MonthView.swift | 28 ++--- 4 files changed, 129 insertions(+), 47 deletions(-) diff --git a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift index deffbcb..02a0f8d 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift +++ b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift @@ -16,7 +16,8 @@ struct ContentView: View { // UserDefaults.standard.set(["en"], forKey: "AppleLanguages") // } - @State private var selectedDate = Date() + @State private var selectedGregorianDate = Date() + @State private var selectedHijriDate = Date() let specialDates: [SpecialDate] = [ @@ -56,19 +57,28 @@ struct ContentView: View { } } + switch calenderType { + case .gregorian: + FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) + // .hideMarkers() + .calenderType(calenderType) + .datePickerTheme(main: + .init( + accentColor: Color(uiColor: UIColor(red: 0.23, green: 0.80, blue: 0.81, alpha: 1.00)), + monthTitle: .white, + daysName: Color(uiColor: UIColor(red: 0.45, green: 0.48, blue: 0.48, alpha: 1.00)), + daysNumbers: Color(uiColor: UIColor(red: 0.83, green: 0.84, blue: 0.84, alpha: 1.00)), + previousDaysNumber: Color(uiColor: UIColor(red: 0.44, green: 0.47, blue: 0.47, alpha: 1.00)), + backgroundColor: Color(uiColor: UIColor(red: 0.22, green: 0.25, blue: 0.25, alpha: 1.00))) + ) + case .hijri: + + FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) + // .hideMarkers() + .calenderType(calenderType) + } - FXDatePickerView(selectedDate: $selectedDate, specialDates: specialDates) - // .hideMarkers() - .calenderType(calenderType) - .datePickerTheme(main: - .init( - accentColor: Color(uiColor: UIColor(red: 0.23, green: 0.80, blue: 0.81, alpha: 1.00)), - monthTitle: .white, - daysName: Color(uiColor: UIColor(red: 0.45, green: 0.48, blue: 0.48, alpha: 1.00)), - daysNumbers: Color(uiColor: UIColor(red: 0.83, green: 0.84, blue: 0.84, alpha: 1.00)), - previousDaysNumber: Color(uiColor: UIColor(red: 0.44, green: 0.47, blue: 0.47, alpha: 1.00)), - backgroundColor: Color(uiColor: UIColor(red: 0.22, green: 0.25, blue: 0.25, alpha: 1.00))) - ) + } diff --git a/Sources/FXDatePicker/Views/DayView.swift b/Sources/FXDatePicker/Views/DayView.swift index a4ccd3d..c319482 100644 --- a/Sources/FXDatePicker/Views/DayView.swift +++ b/Sources/FXDatePicker/Views/DayView.swift @@ -55,6 +55,10 @@ public struct DayView: View { .frame(height: hideMarkers == false ? 50 : 40) } +} + +extension DayView { + @ViewBuilder func specialDateImage(_ specialDate: SpecialDate) -> some View { switch specialDate.dateType { case .image(let imageType): @@ -79,5 +83,3 @@ public struct DayView: View { return formatter } } - - diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index 06cbf97..e780ecb 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -19,6 +19,8 @@ public struct FXDatePickerView: View { private var hideMarkers: Bool = false + @State private var dateRange: [Date] = [] + private var calendar: Calendar { switch calenderType { case .gregorian: @@ -39,8 +41,9 @@ public struct FXDatePickerView: View { self._selectedDate = selectedDate self.specialDates = specialDates } - + public var body: some View { + VStack { HStack { Text(getMonthName(from: displayedMonth)) @@ -65,16 +68,97 @@ public struct FXDatePickerView: View { .padding(.horizontal, 12) .frame(height: 40) - MonthView(displayedMonth: $displayedMonth, - selectedDate: $selectedDate, - specialDates: specialDates, - calendar: calendar, - hideMarkers: hideMarkers) + TabView(selection: $displayedMonth) { + ForEach(dateRange, id: \.self) { month in + MonthView(displayedMonth: .constant(month), + selectedDate: $selectedDate, + specialDates: specialDates, + calendar: calendar, + hideMarkers: hideMarkers) + .id(month) + .onChange(of: displayedMonth) { newMonth in + updateDateRangeIfNeeded(for: newMonth) + } + } + } + .tabViewStyle(.page(indexDisplayMode: .never)) + .frame(height: 320) + } .padding() .background(theme.main.backgroundColor) + .onAppear { + setupCurrentDate() + } + + } + +} + +public extension FXDatePickerView { + + func hideMarkers(_ show: Bool = true) -> FXDatePickerView { + var fxDatePicker = self + fxDatePicker.hideMarkers = show + return fxDatePicker } + private func setupCurrentDate() { + let currentDate = Date() + let startRange = calendar.date(byAdding: .year, value: -3, to: currentDate)! + let endRange = calendar.date(byAdding: .year, value: 3, to: currentDate)! + dateRange = generateMonths(start: startRange, end: endRange) + displayedMonth = currentDate + + } + + private func updateDateRangeIfNeeded(for month: Date) { + // Convert the dateRange array to a Set for efficient contains checking + let dateSet = Set(dateRange) + + let monthIndex = dateRange.firstIndex(of: month) ?? 0 + let totalMonths = dateRange.count + + // Load more months at the start if near the beginning of the range + if monthIndex < 3 { + let newStart = calendar.date(byAdding: .year, value: -1, to: dateRange.first!)! + // Generate new months up to the day before the first month in the range + let newEnd = calendar.date(byAdding: .day, value: -1, to: dateRange.first!)! + let newMonths = generateMonths(start: newStart, end: newEnd) + + // Append only new months that are not already in dateRange + let filteredMonths = newMonths.filter { !dateSet.contains($0) } + dateRange = filteredMonths + dateRange + } + + // Load more months at the end if near the end of the range + if monthIndex > totalMonths - 4 { + // Generate new months starting the day after the last month in the range + let newStart = calendar.date(byAdding: .day, value: 1, to: dateRange.last!)! + let newEnd = calendar.date(byAdding: .year, value: 1, to: dateRange.last!)! + let newMonths = generateMonths(start: newStart, end: newEnd) + + // Append only new months that are not already in dateRange + let filteredMonths = newMonths.filter { !dateSet.contains($0) } + dateRange += filteredMonths + } + } + + + private func generateMonths(start: Date, end: Date) -> [Date] { + var months: [Date] = [] + var currentDate = start + + while currentDate <= end { + months.append(currentDate) + currentDate = calendar.date(byAdding: .month, value: 1, to: currentDate)! + } + + return months + } + + + func changeMonth(by increment: Int) { if let newMonth = calendar.date(byAdding: .month, value: increment, to: displayedMonth) { displayedMonth = newMonth @@ -95,13 +179,3 @@ public struct FXDatePickerView: View { } } - -public extension FXDatePickerView { - - func hideMarkers(_ show: Bool = true) -> FXDatePickerView { - var fxDatePicker = self - fxDatePicker.hideMarkers = show - return fxDatePicker - } - -} diff --git a/Sources/FXDatePicker/Views/MonthView.swift b/Sources/FXDatePicker/Views/MonthView.swift index 1cb9da9..2057545 100644 --- a/Sources/FXDatePicker/Views/MonthView.swift +++ b/Sources/FXDatePicker/Views/MonthView.swift @@ -21,6 +21,12 @@ public struct MonthView: View { let calendar: Calendar let hideMarkers: Bool + private var totalCells: Int { 6 * 7 } // 6 rows, 7 columns + + private var daysOfWeek: [String] { + return calendar.shortWeekdaySymbols + } + private var firstDayOfMonth: Date { calendar.date(from: calendar.dateComponents([.year, .month], from: displayedMonth)) ?? Date() } @@ -75,9 +81,10 @@ public struct MonthView: View { } .frame(height: totalMonthViewHeight) } - } - - + } +} + +extension MonthView { @ViewBuilder private func gridCellView(at index: Int) -> some View { let dayOffset = index - firstDayOfWeekInMonth @@ -98,18 +105,8 @@ public struct MonthView: View { Text("").frame(height: 50) } } - - - - - private var daysOfWeek: [String] { - return calendar.shortWeekdaySymbols - } - - - - private var totalCells: Int { 6 * 7 } // 6 rows, 7 columns - + + private func getDateFor(day: Int) -> Date { calendar.date(from: DateComponents(year: calendar.component(.year, from: displayedMonth), month: calendar.component(.month, from: displayedMonth), day: day))! } @@ -133,5 +130,4 @@ public struct MonthView: View { specialDate.dateString.dateFromString(calendar: calendar) == date }) } - } From 5106116603838986e7decbcd6a9126ebab883c18 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Wed, 3 Jan 2024 11:51:56 +0300 Subject: [PATCH 02/21] Add Swipe Feature, suppourt iOS 14 --- .../project.pbxproj | 4 +- .../FXDatePickerDemo/ContentView.swift | 13 +-- Package.swift | 2 +- .../Modifiers/ThemeColorModifier.swift | 6 +- .../FXDatePicker/Views/FXDatePickerView.swift | 95 ++++++++++--------- Sources/FXDatePicker/Views/SwipeView.swift | 41 ++++++++ 6 files changed, 103 insertions(+), 58 deletions(-) create mode 100644 Sources/FXDatePicker/Views/SwipeView.swift diff --git a/FXDatePickerDemo/FXDatePickerDemo.xcodeproj/project.pbxproj b/FXDatePickerDemo/FXDatePickerDemo.xcodeproj/project.pbxproj index 59c0384..52fb49e 100644 --- a/FXDatePickerDemo/FXDatePickerDemo.xcodeproj/project.pbxproj +++ b/FXDatePickerDemo/FXDatePickerDemo.xcodeproj/project.pbxproj @@ -303,7 +303,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -333,7 +333,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift index 02a0f8d..68765a2 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift +++ b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift @@ -60,16 +60,17 @@ struct ContentView: View { switch calenderType { case .gregorian: FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) - // .hideMarkers() + //.hideMarkers() + // .disableSwipe() .calenderType(calenderType) .datePickerTheme(main: .init( - accentColor: Color(uiColor: UIColor(red: 0.23, green: 0.80, blue: 0.81, alpha: 1.00)), + accentColor: Color(UIColor(red: 0.23, green: 0.80, blue: 0.81, alpha: 1.00)), monthTitle: .white, - daysName: Color(uiColor: UIColor(red: 0.45, green: 0.48, blue: 0.48, alpha: 1.00)), - daysNumbers: Color(uiColor: UIColor(red: 0.83, green: 0.84, blue: 0.84, alpha: 1.00)), - previousDaysNumber: Color(uiColor: UIColor(red: 0.44, green: 0.47, blue: 0.47, alpha: 1.00)), - backgroundColor: Color(uiColor: UIColor(red: 0.22, green: 0.25, blue: 0.25, alpha: 1.00))) + daysName: Color(UIColor(red: 0.45, green: 0.48, blue: 0.48, alpha: 1.00)), + daysNumbers: Color(UIColor(red: 0.83, green: 0.84, blue: 0.84, alpha: 1.00)), + previousDaysNumber: Color(UIColor(red: 0.44, green: 0.47, blue: 0.47, alpha: 1.00)), + backgroundColor: Color(UIColor(red: 0.22, green: 0.25, blue: 0.25, alpha: 1.00))) ) case .hijri: diff --git a/Package.swift b/Package.swift index 8ff4cca..55d8931 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "FXDatePicker", platforms: [ - .iOS(.v15) // Specifies that the package requires iOS 13.0 or newer + .iOS(.v14) // Specifies that the package requires iOS 13.0 or newer ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. diff --git a/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift b/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift index 0c3189b..b4c38e6 100644 --- a/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift +++ b/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift @@ -51,11 +51,11 @@ public extension DatePickerTheme { public let backgroundColor: Color public init(accentColor: Color = .blue, - monthTitle: Color = Color(uiColor: .label), + monthTitle: Color = Color(UIColor.label), daysName: Color = .gray, - daysNumbers: Color = Color(uiColor: .label), + daysNumbers: Color = Color(UIColor.label), previousDaysNumber: Color = .gray, - backgroundColor: Color = Color(uiColor: .systemBackground)) { + backgroundColor: Color = Color(UIColor.systemBackground)) { self.accentColor = accentColor self.monthTitle = monthTitle diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index e780ecb..7365f6d 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -18,9 +18,12 @@ public struct FXDatePickerView: View { @Environment(\.layoutDirection) private var layoutDirection private var hideMarkers: Bool = false + private var disableSwipe: Bool = false @State private var dateRange: [Date] = [] + @State private var shouldUpdateView: Bool = false + private var calendar: Calendar { switch calenderType { case .gregorian: @@ -68,28 +71,26 @@ public struct FXDatePickerView: View { .padding(.horizontal, 12) .frame(height: 40) - TabView(selection: $displayedMonth) { - ForEach(dateRange, id: \.self) { month in - MonthView(displayedMonth: .constant(month), - selectedDate: $selectedDate, - specialDates: specialDates, - calendar: calendar, - hideMarkers: hideMarkers) - .id(month) - .onChange(of: displayedMonth) { newMonth in - updateDateRangeIfNeeded(for: newMonth) - } - } - } - .tabViewStyle(.page(indexDisplayMode: .never)) - .frame(height: 320) - + SwipeView(dateRange: $dateRange, displayedMonth: $displayedMonth, isDisable: disableSwipe) { + MonthView(displayedMonth: $displayedMonth, + selectedDate: $selectedDate, + specialDates: specialDates, + calendar: calendar, + hideMarkers: hideMarkers) + } + .id(shouldUpdateView) + .frame(height: 320) } .padding() .background(theme.main.backgroundColor) .onAppear { setupCurrentDate() } + .onChange(of: displayedMonth) { newMonth in + print("Displayed month changed to: \(newMonth)") + + updateDateRangeIfNeeded(for: newMonth) + } } @@ -103,44 +104,44 @@ public extension FXDatePickerView { return fxDatePicker } + func disableSwipe(_ hide: Bool = true) -> FXDatePickerView { + var fxDatePicker = self + fxDatePicker.disableSwipe = hide + return fxDatePicker + } + + private func setupCurrentDate() { - let currentDate = Date() - let startRange = calendar.date(byAdding: .year, value: -3, to: currentDate)! - let endRange = calendar.date(byAdding: .year, value: 3, to: currentDate)! + // load 6 years when DatePicker appear + let startRange = calendar.date(byAdding: .year, value: -6, to: displayedMonth)! + let endRange = calendar.date(byAdding: .year, value: 6, to: displayedMonth)! dateRange = generateMonths(start: startRange, end: endRange) - displayedMonth = currentDate - } +// Needed for Swipe feature to load more Months private func updateDateRangeIfNeeded(for month: Date) { - // Convert the dateRange array to a Set for efficient contains checking - let dateSet = Set(dateRange) - - let monthIndex = dateRange.firstIndex(of: month) ?? 0 - let totalMonths = dateRange.count - - // Load more months at the start if near the beginning of the range - if monthIndex < 3 { - let newStart = calendar.date(byAdding: .year, value: -1, to: dateRange.first!)! - // Generate new months up to the day before the first month in the range - let newEnd = calendar.date(byAdding: .day, value: -1, to: dateRange.first!)! - let newMonths = generateMonths(start: newStart, end: newEnd) - - // Append only new months that are not already in dateRange - let filteredMonths = newMonths.filter { !dateSet.contains($0) } - dateRange = filteredMonths + dateRange + let monthStart = month + + // Check and extend the end of the range + if let lastMonth = dateRange.last, monthStart > calendar.date(byAdding: .month, value: -6, to: lastMonth)! { + let newEnd = calendar.date(byAdding: .month, value: 6, to: lastMonth)! + let newMonths = generateMonths(start: lastMonth, end: newEnd).filter { !dateRange.contains($0) } + dateRange.append(contentsOf: newMonths) + dateRange = Array(Set(dateRange)).sorted() // Remove duplicates and sort + print("Extended end of range. New range: \(dateRange)") + shouldUpdateView.toggle() + } - // Load more months at the end if near the end of the range - if monthIndex > totalMonths - 4 { - // Generate new months starting the day after the last month in the range - let newStart = calendar.date(byAdding: .day, value: 1, to: dateRange.last!)! - let newEnd = calendar.date(byAdding: .year, value: 1, to: dateRange.last!)! - let newMonths = generateMonths(start: newStart, end: newEnd) + // Check and extend the start of the range + if let firstMonth = dateRange.first, monthStart < calendar.date(byAdding: .month, value: 6, to: firstMonth)! { + let newStart = calendar.date(byAdding: .month, value: -6, to: firstMonth)! + let newMonths = generateMonths(start: newStart, end: firstMonth).filter { !dateRange.contains($0) } + dateRange.insert(contentsOf: newMonths, at: 0) + dateRange = Array(Set(dateRange)).sorted() // Remove duplicates and sort + print("Extended start of range. New range: \(dateRange)") + shouldUpdateView.toggle() - // Append only new months that are not already in dateRange - let filteredMonths = newMonths.filter { !dateSet.contains($0) } - dateRange += filteredMonths } } @@ -179,3 +180,5 @@ public extension FXDatePickerView { } } + + diff --git a/Sources/FXDatePicker/Views/SwipeView.swift b/Sources/FXDatePicker/Views/SwipeView.swift new file mode 100644 index 0000000..3ac8f08 --- /dev/null +++ b/Sources/FXDatePicker/Views/SwipeView.swift @@ -0,0 +1,41 @@ +//// +//SwipeView.swift +// +// +//Created by Basel Baragabah on 03/01/2024. +//Copyright © 2024 Basel Baragabah. All rights reserved. +// + +import SwiftUI + +struct SwipeView: View { + let content: () -> Content + var isDisable: Bool + @Binding var displayedMonth: Date + @Binding var dateRange: [Date] + var month: Int? + + init(dateRange: Binding<[Date]> ,displayedMonth: Binding, isDisable: Bool, @ViewBuilder content: @escaping () -> Content) { + self._dateRange = dateRange + self._displayedMonth = displayedMonth + self.isDisable = isDisable + self.content = content + } + + var body: some View { + Group { + if isDisable == false { + TabView(selection: $displayedMonth) { + ForEach($dateRange, id: \.self) { $month in + content() + .tag(month) + } + } + .tabViewStyle(.page(indexDisplayMode: .never)) + + } else { + content() + } + } + } +} From 43a1a7185c0585dc1d2a847ff49a3c244319e2cf Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Wed, 3 Jan 2024 11:53:30 +0300 Subject: [PATCH 03/21] Add Swipe Feature, suppourt iOS 14, remove logs --- Sources/FXDatePicker/Views/FXDatePickerView.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index 7365f6d..869694c 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -87,8 +87,6 @@ public struct FXDatePickerView: View { setupCurrentDate() } .onChange(of: displayedMonth) { newMonth in - print("Displayed month changed to: \(newMonth)") - updateDateRangeIfNeeded(for: newMonth) } From 6d7170d9d9a2abcf21184d32b98b39bc42ef3669 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Wed, 3 Jan 2024 12:03:57 +0300 Subject: [PATCH 04/21] Update README.md --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b81511e..77ef078 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,22 @@ Here's how it looks with the hideMarkers() modifier applied: With this modifier, the FXDatePickerView will render without showing any markers associated with the specialDates. This keeps the calendar view simple and focused on basic date picking functionality. +-- + +### disableSwipe + +The `.disableSwipe()` modifier is used with the `FXDatePickerView` to disable the swipe gesture functionality. This can be particularly useful in scenarios where you want to prevent users from changing the date by swiping, ensuring that navigation through dates is controlled through other means (like buttons or external controls). + +Example +To apply this modifier, simply chain `.disableSwipe()` to your `FXDatePickerView` instance. Here’s how you can do it: + +```swift +FXDatePickerView(selectedDate: $selectedDate, specialDates: specialDates) + .disableSwipe() +``` +In this example, the `FXDatePickerView` will no longer respond to swipe gestures for date navigation, and users will need to use buttons provided in the UI to change the selected date. + + ## Installation ### [Swift Package Manager](https://swift.org/package-manager/) @@ -114,6 +130,6 @@ dependencies: [ ## Requirements -* iOS 15+ +* iOS 14+ * Xcode 13+ From 2c602ebca29e9c420c7c2a4c96cd152c13ba0f1f Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Wed, 3 Jan 2024 12:16:12 +0300 Subject: [PATCH 05/21] fix swipe --- .../FXDatePickerDemo/ContentView.swift | 2 +- .../FXDatePicker/Views/FXDatePickerView.swift | 23 +++++++++++++++---- Sources/FXDatePicker/Views/SwipeView.swift | 5 +--- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift index 68765a2..cd83b2a 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift +++ b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift @@ -60,7 +60,7 @@ struct ContentView: View { switch calenderType { case .gregorian: FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) - //.hideMarkers() + // .hideMarkers() // .disableSwipe() .calenderType(calenderType) .datePickerTheme(main: diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index 869694c..215a633 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -72,11 +72,24 @@ public struct FXDatePickerView: View { .frame(height: 40) SwipeView(dateRange: $dateRange, displayedMonth: $displayedMonth, isDisable: disableSwipe) { - MonthView(displayedMonth: $displayedMonth, - selectedDate: $selectedDate, - specialDates: specialDates, - calendar: calendar, - hideMarkers: hideMarkers) + + if disableSwipe { + MonthView(displayedMonth: $displayedMonth, + selectedDate: $selectedDate, + specialDates: specialDates, + calendar: calendar, + hideMarkers: hideMarkers) + } else { + ForEach($dateRange, id: \.self) { $month in + MonthView(displayedMonth: $month, + selectedDate: $selectedDate, + specialDates: specialDates, + calendar: calendar, + hideMarkers: hideMarkers) + .tag(month) + } + } + } .id(shouldUpdateView) .frame(height: 320) diff --git a/Sources/FXDatePicker/Views/SwipeView.swift b/Sources/FXDatePicker/Views/SwipeView.swift index 3ac8f08..f3b028a 100644 --- a/Sources/FXDatePicker/Views/SwipeView.swift +++ b/Sources/FXDatePicker/Views/SwipeView.swift @@ -13,7 +13,6 @@ struct SwipeView: View { var isDisable: Bool @Binding var displayedMonth: Date @Binding var dateRange: [Date] - var month: Int? init(dateRange: Binding<[Date]> ,displayedMonth: Binding, isDisable: Bool, @ViewBuilder content: @escaping () -> Content) { self._dateRange = dateRange @@ -26,10 +25,8 @@ struct SwipeView: View { Group { if isDisable == false { TabView(selection: $displayedMonth) { - ForEach($dateRange, id: \.self) { $month in content() - .tag(month) - } + } .tabViewStyle(.page(indexDisplayMode: .never)) From fc2939a3e74d76f726016e02efd49c2dcd5ca8c7 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Wed, 3 Jan 2024 13:00:06 +0300 Subject: [PATCH 06/21] fixed the week header --- .../FXDatePickerDemo/ContentView.swift | 27 ++++++------ .../FXDatePicker/Views/FXDatePickerView.swift | 44 +++++++++++++------ Sources/FXDatePicker/Views/MonthView.swift | 14 ------ 3 files changed, 44 insertions(+), 41 deletions(-) diff --git a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift index cd83b2a..9a2d3cb 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift +++ b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift @@ -11,10 +11,10 @@ import FXDatePicker struct ContentView: View { -// init() { -// // Change the app language to Arabic -// UserDefaults.standard.set(["en"], forKey: "AppleLanguages") -// } + init() { + // Change the app language to Arabic + UserDefaults.standard.set(["ar"], forKey: "AppleLanguages") + } @State private var selectedGregorianDate = Date() @State private var selectedHijriDate = Date() @@ -23,7 +23,7 @@ struct ContentView: View { SpecialDate(dateString: "19/6/1445", dateType: .sfSymbols(SFSymbolsType( imageName: "figure.walk", color: .red))), - SpecialDate(dateString: "2/1/2024", dateType: .sfSymbols(SFSymbolsType( imageName: "airplane.departure", color: .blue))), + SpecialDate(dateString: "2/1/2024", dateType: .sfSymbols(SFSymbolsType( imageName: "airplane.departure", color: .red))), SpecialDate(dateString: "11/1/2024", dateType: .sfSymbols(SFSymbolsType(imageName: "phone.fill", color: .orange))), @@ -43,7 +43,7 @@ struct ContentView: View { var body: some View { VStack { - + Picker("", selection: $selectedCalender) { Text("Gregorian").tag(0) Text("Hijri").tag(1) @@ -65,18 +65,19 @@ struct ContentView: View { .calenderType(calenderType) .datePickerTheme(main: .init( - accentColor: Color(UIColor(red: 0.23, green: 0.80, blue: 0.81, alpha: 1.00)), + accentColor: Color(UIColor(red: 0.49, green: 0.76, blue: 0.96, alpha: 1.00)), monthTitle: .white, - daysName: Color(UIColor(red: 0.45, green: 0.48, blue: 0.48, alpha: 1.00)), - daysNumbers: Color(UIColor(red: 0.83, green: 0.84, blue: 0.84, alpha: 1.00)), - previousDaysNumber: Color(UIColor(red: 0.44, green: 0.47, blue: 0.47, alpha: 1.00)), - backgroundColor: Color(UIColor(red: 0.22, green: 0.25, blue: 0.25, alpha: 1.00))) + daysName: Color(UIColor(red: 0.96, green: 0.98, blue: 0.99, alpha: 1.00)), + daysNumbers: Color(UIColor(red: 0.96, green: 0.98, blue: 0.99, alpha: 1.00)), + previousDaysNumber: Color(UIColor(red: 0.19, green: 0.47, blue: 0.76, alpha: 1.00)), + backgroundColor: Color(UIColor(red: 0.01, green: 0.33, blue: 0.64, alpha: 1.00))) ) case .hijri: FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) - // .hideMarkers() - .calenderType(calenderType) + .hideMarkers() + .disableSwipe() + .calenderType(calenderType) } diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index 215a633..af1eded 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -24,6 +24,10 @@ public struct FXDatePickerView: View { @State private var shouldUpdateView: Bool = false + private var daysOfWeek: [String] { + return calendar.shortWeekdaySymbols + } + private var calendar: Calendar { switch calenderType { case .gregorian: @@ -71,28 +75,40 @@ public struct FXDatePickerView: View { .padding(.horizontal, 12) .frame(height: 40) - SwipeView(dateRange: $dateRange, displayedMonth: $displayedMonth, isDisable: disableSwipe) { + VStack { + HStack { + ForEach(daysOfWeek, id: \.self) { day in + Text(day) + .frame(maxWidth: .infinity) + .font(.caption) + .foregroundColor(theme.main.daysName) + } + } + - if disableSwipe { - MonthView(displayedMonth: $displayedMonth, - selectedDate: $selectedDate, - specialDates: specialDates, - calendar: calendar, - hideMarkers: hideMarkers) - } else { - ForEach($dateRange, id: \.self) { $month in - MonthView(displayedMonth: $month, + SwipeView(dateRange: $dateRange, displayedMonth: $displayedMonth, isDisable: disableSwipe) { + + if disableSwipe { + MonthView(displayedMonth: $displayedMonth, selectedDate: $selectedDate, specialDates: specialDates, calendar: calendar, hideMarkers: hideMarkers) - .tag(month) + } else { + ForEach($dateRange, id: \.self) { $month in + MonthView(displayedMonth: $month, + selectedDate: $selectedDate, + specialDates: specialDates, + calendar: calendar, + hideMarkers: hideMarkers) + .tag(month) + } } + } - + .id(shouldUpdateView) + .frame(height: 320) } - .id(shouldUpdateView) - .frame(height: 320) } .padding() .background(theme.main.backgroundColor) diff --git a/Sources/FXDatePicker/Views/MonthView.swift b/Sources/FXDatePicker/Views/MonthView.swift index 2057545..127ad65 100644 --- a/Sources/FXDatePicker/Views/MonthView.swift +++ b/Sources/FXDatePicker/Views/MonthView.swift @@ -23,9 +23,6 @@ public struct MonthView: View { private var totalCells: Int { 6 * 7 } // 6 rows, 7 columns - private var daysOfWeek: [String] { - return calendar.shortWeekdaySymbols - } private var firstDayOfMonth: Date { calendar.date(from: calendar.dateComponents([.year, .month], from: displayedMonth)) ?? Date() @@ -64,23 +61,12 @@ public struct MonthView: View { let totalPaddingHeight = totalMonthViewHeight - totalRowHeight let paddingPerRow = totalPaddingHeight / CGFloat(maxRows - 2) - VStack { - HStack { - ForEach(daysOfWeek, id: \.self) { day in - Text(day) - .frame(maxWidth: .infinity) - .font(.caption) - .foregroundColor(theme.main.daysName) - } - } - LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 7), spacing: paddingPerRow) { ForEach(0..<(numberOfRows * 7), id: \.self) { index in gridCellView(at: index) } } .frame(height: totalMonthViewHeight) - } } } From 3e210e15d1ddd4d1d02f5b29f9b9f7ef9245ada4 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Thu, 4 Jan 2024 09:38:18 +0300 Subject: [PATCH 07/21] Add SelectMonthPickerView, to allow user change month and year --- .../FXDatePickerDemo/ContentView.swift | 2 +- .../FXDatePicker/Extensions/Bold+Ext.swift | 3 +- Sources/FXDatePicker/UIKit/PickerView.swift | 72 +++++++++ Sources/FXDatePicker/Views/DayView.swift | 1 - .../FXDatePicker/Views/FXDatePickerView.swift | 150 ++++++++++++------ .../Views/SelectMonthPickerView.swift | 104 ++++++++++++ 6 files changed, 277 insertions(+), 55 deletions(-) create mode 100644 Sources/FXDatePicker/UIKit/PickerView.swift create mode 100644 Sources/FXDatePicker/Views/SelectMonthPickerView.swift diff --git a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift index 9a2d3cb..7e48b31 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift +++ b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift @@ -13,7 +13,7 @@ struct ContentView: View { init() { // Change the app language to Arabic - UserDefaults.standard.set(["ar"], forKey: "AppleLanguages") + // UserDefaults.standard.set(["ar"], forKey: "AppleLanguages") } @State private var selectedGregorianDate = Date() diff --git a/Sources/FXDatePicker/Extensions/Bold+Ext.swift b/Sources/FXDatePicker/Extensions/Bold+Ext.swift index 9949d96..d8c1c87 100644 --- a/Sources/FXDatePicker/Extensions/Bold+Ext.swift +++ b/Sources/FXDatePicker/Extensions/Bold+Ext.swift @@ -12,9 +12,8 @@ public extension View { @ViewBuilder func toBold(_ condition: Bool = true) -> some View { if condition { if #available(iOS 16.0, *) { - self.bold() // New method available in iOS 16 + self.bold() } else { - // Alternative for older versions self.font(Font.body.bold()) } } else { diff --git a/Sources/FXDatePicker/UIKit/PickerView.swift b/Sources/FXDatePicker/UIKit/PickerView.swift new file mode 100644 index 0000000..d00ffd6 --- /dev/null +++ b/Sources/FXDatePicker/UIKit/PickerView.swift @@ -0,0 +1,72 @@ +//// +//PickerView.swift +// +// +//Created by Basel Baragabah on 03/01/2024. +//Copyright © 2024 Basel Baragabah. All rights reserved. +// + +import SwiftUI + + +struct PickerView: UIViewRepresentable { + var data: [[String]] + @Binding var selections: [Int] + var textColor: UIColor = .black + + func makeCoordinator() -> PickerView.Coordinator { + Coordinator(self) + } + + func makeUIView(context: UIViewRepresentableContext) -> UIPickerView { + let picker = UIPickerView(frame: .zero) + + picker.dataSource = context.coordinator + picker.delegate = context.coordinator + + return picker + } + + func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext) { + for i in 0...(self.selections.count - 1) { + view.selectRow(self.selections[i], inComponent: i, animated: false) + } + context.coordinator.parent = self + } + + class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate { + var parent: PickerView + + init(_ pickerView: PickerView) { + self.parent = pickerView + } + + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return self.parent.data.count + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return self.parent.data[component].count + } + + func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { + var label: UILabel + if let reuseLabel = view as? UILabel { + label = reuseLabel + } else { + label = UILabel() + } + + label.text = self.parent.data[component][row] + label.textColor = parent.textColor + label.textAlignment = .center + label.font = UIFont.systemFont(ofSize: 24) + + return label + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + self.parent.selections[component] = row + } + } +} diff --git a/Sources/FXDatePicker/Views/DayView.swift b/Sources/FXDatePicker/Views/DayView.swift index c319482..98859a1 100644 --- a/Sources/FXDatePicker/Views/DayView.swift +++ b/Sources/FXDatePicker/Views/DayView.swift @@ -48,7 +48,6 @@ public struct DayView: View { } } - Spacer() } .frame(height: imageSize) } diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index af1eded..76717d7 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -18,12 +18,16 @@ public struct FXDatePickerView: View { @Environment(\.layoutDirection) private var layoutDirection private var hideMarkers: Bool = false - private var disableSwipe: Bool = false - + @State private var disableSwipe: Bool = false + @State private var dateRange: [Date] = [] - + @State private var shouldUpdateView: Bool = false - + + @State private var openShowSelectedMonths: Bool = false + + @State private var arrowRotation: Double = 0 + private var daysOfWeek: [String] { return calendar.shortWeekdaySymbols } @@ -44,48 +48,73 @@ public struct FXDatePickerView: View { } - public init(selectedDate: Binding, specialDates: [SpecialDate]) { + public init(selectedDate: Binding, specialDates: [SpecialDate]) { self._selectedDate = selectedDate self.specialDates = specialDates } - + public var body: some View { - + VStack { + HStack { - Text(getMonthName(from: displayedMonth)) - .toBold() - .foregroundColor(theme.main.monthTitle) - Spacer() - Button(action: { changeMonth(by: -1) }) { - Image(systemName: layoutDirection == .leftToRight ? "chevron.left" : "chevron.right") - .toBold() - .foregroundColor(theme.main.accentColor) - } - .padding(.horizontal) + Button(action: { + withAnimation { + arrowRotation = arrowRotation == 0 ? 90 : 0 + openShowSelectedMonths.toggle() + } + + }, label: { + + HStack(spacing: 5) { + + Text(getMonthName(from: displayedMonth)) + .toBold() + .foregroundColor(theme.main.monthTitle) + + Image(systemName: layoutDirection == .rightToLeft ? "chevron.left" : "chevron.right" ) + .font(.system(size: 12)) + .toBold() + .rotationEffect(.degrees(arrowRotation)) + .foregroundColor(theme.main.accentColor) + } + }) + + Spacer() - Button(action: { changeMonth(by: 1) }) { - Image(systemName: layoutDirection == .leftToRight ? "chevron.right" : "chevron.left") - .toBold() - .foregroundColor(theme.main.accentColor) + if !openShowSelectedMonths { + + Button(action: { changeMonth(by: -1) }) { + Image(systemName: layoutDirection == .leftToRight ? "chevron.left" : "chevron.right") + .toBold() + .foregroundColor(theme.main.accentColor) + } + .padding(.horizontal) + + Button(action: { changeMonth(by: 1) }) { + Image(systemName: layoutDirection == .leftToRight ? "chevron.right" : "chevron.left") + .toBold() + .foregroundColor(theme.main.accentColor) + } } } .padding(.horizontal, 12) .frame(height: 40) VStack { - HStack { - ForEach(daysOfWeek, id: \.self) { day in - Text(day) - .frame(maxWidth: .infinity) - .font(.caption) - .foregroundColor(theme.main.daysName) + if !openShowSelectedMonths { + HStack { + ForEach(daysOfWeek, id: \.self) { day in + Text(day) + .frame(maxWidth: .infinity) + .font(.caption) + .foregroundColor(theme.main.daysName) + } } } - SwipeView(dateRange: $dateRange, displayedMonth: $displayedMonth, isDisable: disableSwipe) { if disableSwipe { @@ -107,7 +136,28 @@ public struct FXDatePickerView: View { } .id(shouldUpdateView) - .frame(height: 320) + .frame(height: openShowSelectedMonths != true ? 320 : 342) + .overlay( + + openShowSelectedMonths ? SelectMonthPickerView(selectedDate: $selectedDate, calendar: calendar, calenderType: calenderType) + .onChange(of: selectedDate, perform: { value in + disableSwipe = true + displayedMonth = value + setupCurrentDate() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { + disableSwipe = false + }) + + + }) + + : nil + + + + , alignment: .center) + } } .padding() @@ -118,9 +168,9 @@ public struct FXDatePickerView: View { .onChange(of: displayedMonth) { newMonth in updateDateRangeIfNeeded(for: newMonth) } - + } - + } public extension FXDatePickerView { @@ -132,7 +182,7 @@ public extension FXDatePickerView { } func disableSwipe(_ hide: Bool = true) -> FXDatePickerView { - var fxDatePicker = self + let fxDatePicker = self fxDatePicker.disableSwipe = hide return fxDatePicker } @@ -145,10 +195,10 @@ public extension FXDatePickerView { dateRange = generateMonths(start: startRange, end: endRange) } -// Needed for Swipe feature to load more Months + // Needed for Swipe feature to load more Months private func updateDateRangeIfNeeded(for month: Date) { let monthStart = month - + // Check and extend the end of the range if let lastMonth = dateRange.last, monthStart > calendar.date(byAdding: .month, value: -6, to: lastMonth)! { let newEnd = calendar.date(byAdding: .month, value: 6, to: lastMonth)! @@ -157,9 +207,9 @@ public extension FXDatePickerView { dateRange = Array(Set(dateRange)).sorted() // Remove duplicates and sort print("Extended end of range. New range: \(dateRange)") shouldUpdateView.toggle() - + } - + // Check and extend the start of the range if let firstMonth = dateRange.first, monthStart < calendar.date(byAdding: .month, value: 6, to: firstMonth)! { let newStart = calendar.date(byAdding: .month, value: -6, to: firstMonth)! @@ -168,25 +218,23 @@ public extension FXDatePickerView { dateRange = Array(Set(dateRange)).sorted() // Remove duplicates and sort print("Extended start of range. New range: \(dateRange)") shouldUpdateView.toggle() - + } } - + private func generateMonths(start: Date, end: Date) -> [Date] { - var months: [Date] = [] - var currentDate = start - - while currentDate <= end { - months.append(currentDate) - currentDate = calendar.date(byAdding: .month, value: 1, to: currentDate)! - } - - return months - } - - - + var months: [Date] = [] + var currentDate = start + + while currentDate <= end { + months.append(currentDate) + currentDate = calendar.date(byAdding: .month, value: 1, to: currentDate)! + } + + return months + } + func changeMonth(by increment: Int) { if let newMonth = calendar.date(byAdding: .month, value: increment, to: displayedMonth) { displayedMonth = newMonth diff --git a/Sources/FXDatePicker/Views/SelectMonthPickerView.swift b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift new file mode 100644 index 0000000..71b01ff --- /dev/null +++ b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift @@ -0,0 +1,104 @@ +//// +//SwiftUIView.swift +// +// +//Created by Basel Baragabah on 03/01/2024. +//Copyright © 2024 Basel Baragabah. All rights reserved. +// + +import SwiftUI + +struct SelectMonthPickerView: View { + + @Environment(\.datePickerTheme) private var theme + let calenderType: CalenderType + + let calendar: Calendar + @Binding var selectedDate: Date + + + private var years: [Int] { + switch calenderType { + case .hijri: + return Array(1300...1500) + default: + return Array(1900...2100) + } + } + + + private var months: [String] + + private var pickerData: [[String]] = [[]] + @State private var selections: [Int] = [] + + init(selectedDate: Binding, calendar: Calendar, calenderType: CalenderType) { + self.calendar = calendar + self._selectedDate = selectedDate + self.months = calendar.monthSymbols + self.calenderType = calenderType + + let currentYear = calendar.component(.year, from: selectedDate.wrappedValue) + let currentMonth = calendar.component(.month, from: selectedDate.wrappedValue) - 1 + + let yearStrings = years.map { String($0) } + let monthStrings = months.map { String($0) } + pickerData = [yearStrings, monthStrings] + + let yearIndex = years.firstIndex(of: currentYear) ?? 0 + self._selections = State(initialValue: [yearIndex, currentMonth]) + } + + + var body: some View { + ZStack { + theme.main.backgroundColor + .ignoresSafeArea() + + PickerView(data: self.pickerData, + selections: self.$selections, + textColor: UIColor(theme.main.monthTitle)) + .onChange(of: selections) { value in + updateSelectedDate(with: value) + } + } + + + } +} + +extension SelectMonthPickerView { + + func updateSelectedDate(with selections: [Int]) { + var components = DateComponents() + + let yearIndex = selections[0] + let monthIndex = selections[1] + let dayIndex = calendar.component(.day, from: selectedDate) + + components.year = years[yearIndex] + components.month = monthIndex + 1 + + let newDate = calendar.date(from: components) + + let newMonthRange = calendar.range(of: .day, in: .month, for: newDate!) + + // If the selected day does exist in the new month, it will be the default day of the month + if dayIndex <= newMonthRange!.count { + components.day = dayIndex + } else { + // If the selected day doesn't exist in the new month, default will move to the first day of the month + components.day = 1 + } + + if let updatedDate = calendar.date(from: components) { + selectedDate = updatedDate + } else { + print("Failed to create date") + } + } +} + +#Preview { + SelectMonthPickerView(selectedDate: .constant(Date()), calendar: Calendar.current, calenderType: .gregorian) +} From 8db6027ba717cc5b28d4d674a010b9eb72893fb4 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Thu, 4 Jan 2024 09:53:40 +0300 Subject: [PATCH 08/21] change years number depend on app language --- .../FXDatePickerDemo/ContentView.swift | 2 +- .../Views/SelectMonthPickerView.swift | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift index 7e48b31..db8cc27 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift +++ b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift @@ -13,7 +13,7 @@ struct ContentView: View { init() { // Change the app language to Arabic - // UserDefaults.standard.set(["ar"], forKey: "AppleLanguages") + UserDefaults.standard.set(["en"], forKey: "AppleLanguages") } @State private var selectedGregorianDate = Date() diff --git a/Sources/FXDatePicker/Views/SelectMonthPickerView.swift b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift index 71b01ff..28c881d 100644 --- a/Sources/FXDatePicker/Views/SelectMonthPickerView.swift +++ b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift @@ -16,7 +16,6 @@ struct SelectMonthPickerView: View { let calendar: Calendar @Binding var selectedDate: Date - private var years: [Int] { switch calenderType { case .hijri: @@ -26,6 +25,18 @@ struct SelectMonthPickerView: View { } } + private var numberFormatter: NumberFormatter { + let formatter = NumberFormatter() + let currentLocale = Locale.current + + if currentLocale.languageCode == "ar" { + formatter.locale = Locale(identifier: "ar") + } else { + formatter.locale = Locale(identifier: "en") + } + + return formatter + } private var months: [String] @@ -41,7 +52,7 @@ struct SelectMonthPickerView: View { let currentYear = calendar.component(.year, from: selectedDate.wrappedValue) let currentMonth = calendar.component(.month, from: selectedDate.wrappedValue) - 1 - let yearStrings = years.map { String($0) } + let yearStrings = years.map { numberFormatter.string(from: NSNumber(value: $0)) ?? "" } let monthStrings = months.map { String($0) } pickerData = [yearStrings, monthStrings] From 7fc7206fa41bd896f2ad68ecff05dcf9b2d95041 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Thu, 4 Jan 2024 11:17:55 +0300 Subject: [PATCH 09/21] add BackgroundStyle, add hideDatePicker --- .../FXDatePickerDemo/ContentView.swift | 35 +++++++-- .../FXDatePicker/Extensions/View+Ext.swift | 28 +++++++ .../Modifiers/ThemeColorModifier.swift | 13 +++- Sources/FXDatePicker/UIKit/PickerView.swift | 14 ++-- Sources/FXDatePicker/Views/DayView.swift | 21 ++--- .../FXDatePicker/Views/FXBackgroundView.swift | 29 +++++++ .../FXDatePicker/Views/FXDatePickerView.swift | 77 +++++++++++-------- .../Views/SelectMonthPickerView.swift | 7 +- 8 files changed, 163 insertions(+), 61 deletions(-) create mode 100644 Sources/FXDatePicker/Extensions/View+Ext.swift create mode 100644 Sources/FXDatePicker/Views/FXBackgroundView.swift diff --git a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift index db8cc27..1ef07c6 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift +++ b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift @@ -60,24 +60,45 @@ struct ContentView: View { switch calenderType { case .gregorian: FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) - // .hideMarkers() + //.hideMarkers() + //.hideDatePicker() // .disableSwipe() .calenderType(calenderType) .datePickerTheme(main: .init( - accentColor: Color(UIColor(red: 0.49, green: 0.76, blue: 0.96, alpha: 1.00)), - monthTitle: .white, - daysName: Color(UIColor(red: 0.96, green: 0.98, blue: 0.99, alpha: 1.00)), - daysNumbers: Color(UIColor(red: 0.96, green: 0.98, blue: 0.99, alpha: 1.00)), - previousDaysNumber: Color(UIColor(red: 0.19, green: 0.47, blue: 0.76, alpha: 1.00)), - backgroundColor: Color(UIColor(red: 0.01, green: 0.33, blue: 0.64, alpha: 1.00))) + accentColor: Color(UIColor(red: 0.82, green: 0.26, blue: 0.42, alpha: 1.00)), + monthTitle: Color(UIColor(red: 0.86, green: 0.87, blue: 0.88, alpha: 1.00)), + daysName: Color(UIColor(red: 0.80, green: 0.80, blue: 0.83, alpha: 1.00)), + daysNumbers: Color(UIColor(red: 0.83, green: 0.83, blue: 0.87, alpha: 1.00)), + previousDaysNumber: Color(UIColor(red: 0.66, green: 0.66, blue: 0.69, alpha: 1.00)), + backgroundStyle: + .radialGradient( + Gradient(colors: + [Color(UIColor(red: 0.19, green: 0.19, blue: 0.22, alpha: 1.00)), + Color(UIColor(red: 0.11, green: 0.11, blue: 0.14, alpha: 1.00)) + ] + ), + center: .center, + startRadius: 1, + endRadius: 300) + ) ) + case .hijri: FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) .hideMarkers() .disableSwipe() .calenderType(calenderType) + .datePickerTheme(main: + .init( + accentColor: Color(UIColor(red: 0.49, green: 0.76, blue: 0.96, alpha: 1.00)), + monthTitle: .white, + daysName: Color(UIColor(red: 0.96, green: 0.98, blue: 0.99, alpha: 1.00)), + daysNumbers: Color(UIColor(red: 0.96, green: 0.98, blue: 0.99, alpha: 1.00)), + previousDaysNumber: Color(UIColor(red: 0.19, green: 0.47, blue: 0.76, alpha: 1.00)), + backgroundStyle: .color(Color(UIColor(red: 0.01, green: 0.33, blue: 0.64, alpha: 1.00)))) + ) } diff --git a/Sources/FXDatePicker/Extensions/View+Ext.swift b/Sources/FXDatePicker/Extensions/View+Ext.swift new file mode 100644 index 0000000..160a028 --- /dev/null +++ b/Sources/FXDatePicker/Extensions/View+Ext.swift @@ -0,0 +1,28 @@ +//// +//SwiftUIView.swift +// +// +//Created by Basel Baragabah on 04/01/2024. +//Copyright © 2024 Basel Baragabah. All rights reserved. +// + +import SwiftUI + +extension View { + @ViewBuilder func background(_ background: BackgroundStyle) -> some View { + switch background { + case .color(let color): + self.overlay(Color.clear) + .background(color) + case .linearGradient(let gradient, let startPoint, let endPoint): + self.overlay(Color.clear) + .background(LinearGradient(gradient: gradient, startPoint: startPoint, endPoint: endPoint)) + case .radialGradient(let gradient, let center, let startRadius, let endRadius): + self.overlay(Color.clear) + .background(RadialGradient(gradient: gradient, center: center, startRadius: startRadius, endRadius: endRadius)) + case .angularGradient(let gradient, let center): + self.overlay(Color.clear) + .background(AngularGradient(gradient: gradient, center: center)) + } + } +} diff --git a/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift b/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift index b4c38e6..6f9b852 100644 --- a/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift +++ b/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift @@ -41,6 +41,7 @@ public struct DatePickerTheme { } } + public extension DatePickerTheme { struct Main { public let accentColor: Color @@ -48,23 +49,29 @@ public extension DatePickerTheme { public let daysName: Color public let daysNumbers: Color public let previousDaysNumber: Color - public let backgroundColor: Color + public let backgroundStyle: BackgroundStyle public init(accentColor: Color = .blue, monthTitle: Color = Color(UIColor.label), daysName: Color = .gray, daysNumbers: Color = Color(UIColor.label), previousDaysNumber: Color = .gray, - backgroundColor: Color = Color(UIColor.systemBackground)) { + backgroundStyle: BackgroundStyle = .color(Color(UIColor.systemBackground))) { self.accentColor = accentColor self.monthTitle = monthTitle self.daysName = daysName self.daysNumbers = daysNumbers self.previousDaysNumber = previousDaysNumber - self.backgroundColor = backgroundColor + self.backgroundStyle = backgroundStyle } } } +public enum BackgroundStyle { + case color(Color) + case linearGradient(Gradient, startPoint: UnitPoint, endPoint: UnitPoint) + case radialGradient(Gradient, center: UnitPoint, startRadius: CGFloat, endRadius: CGFloat) + case angularGradient(Gradient, center: UnitPoint) +} diff --git a/Sources/FXDatePicker/UIKit/PickerView.swift b/Sources/FXDatePicker/UIKit/PickerView.swift index d00ffd6..9bccef2 100644 --- a/Sources/FXDatePicker/UIKit/PickerView.swift +++ b/Sources/FXDatePicker/UIKit/PickerView.swift @@ -9,16 +9,16 @@ import SwiftUI -struct PickerView: UIViewRepresentable { +struct FXPickerView: UIViewRepresentable { var data: [[String]] @Binding var selections: [Int] var textColor: UIColor = .black - func makeCoordinator() -> PickerView.Coordinator { + func makeCoordinator() -> FXPickerView.Coordinator { Coordinator(self) } - func makeUIView(context: UIViewRepresentableContext) -> UIPickerView { + func makeUIView(context: UIViewRepresentableContext) -> UIPickerView { let picker = UIPickerView(frame: .zero) picker.dataSource = context.coordinator @@ -27,7 +27,7 @@ struct PickerView: UIViewRepresentable { return picker } - func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext) { + func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext) { for i in 0...(self.selections.count - 1) { view.selectRow(self.selections[i], inComponent: i, animated: false) } @@ -35,13 +35,13 @@ struct PickerView: UIViewRepresentable { } class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate { - var parent: PickerView + var parent: FXPickerView - init(_ pickerView: PickerView) { + init(_ pickerView: FXPickerView) { self.parent = pickerView } - func numberOfComponents(in pickerView: UIPickerView) -> Int { + func numberOfComponents(in FXPickerView: UIPickerView) -> Int { return self.parent.data.count } diff --git a/Sources/FXDatePicker/Views/DayView.swift b/Sources/FXDatePicker/Views/DayView.swift index 98859a1..8ca79b7 100644 --- a/Sources/FXDatePicker/Views/DayView.swift +++ b/Sources/FXDatePicker/Views/DayView.swift @@ -40,18 +40,21 @@ public struct DayView: View { .cornerRadius(20) } - ZStack { - - if !hideMarkers { - if let specialDate = specialDate { - specialDateImage(specialDate) - } + if !hideMarkers { + ZStack { + + if let specialDate = specialDate { + specialDateImage(specialDate) + .frame(height: imageSize) + .padding(.vertical, 10) + } + + } - + .frame(height: imageSize + 10) } - .frame(height: imageSize) } - .frame(height: hideMarkers == false ? 50 : 40) + .frame(height: hideMarkers == false ? 50 : 40) } } diff --git a/Sources/FXDatePicker/Views/FXBackgroundView.swift b/Sources/FXDatePicker/Views/FXBackgroundView.swift new file mode 100644 index 0000000..148035a --- /dev/null +++ b/Sources/FXDatePicker/Views/FXBackgroundView.swift @@ -0,0 +1,29 @@ +//// +//FXBackgroundView.swift +// +// +//Created by Basel Baragabah on 04/01/2024. +//Copyright © 2024 Basel Baragabah. All rights reserved. +// + +import SwiftUI + + struct FXBackgroundView: View { + let background: BackgroundStyle + + var body: some View { + ZStack { + switch background { + case .color(let color): + color + case .linearGradient(let gradient, let startPoint, let endPoint): + LinearGradient(gradient: gradient, startPoint: startPoint, endPoint: endPoint) + case .radialGradient(let gradient, let center, let startRadius, let endRadius): + RadialGradient(gradient: gradient, center: center, startRadius: startRadius, endRadius: endRadius) + case .angularGradient(let gradient, let center): + AngularGradient(gradient: gradient, center: center) + } + } + } +} + diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index 76717d7..a525315 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -19,7 +19,8 @@ public struct FXDatePickerView: View { private var hideMarkers: Bool = false @State private var disableSwipe: Bool = false - + private var hideDatePicker: Bool = false + @State private var dateRange: [Date] = [] @State private var shouldUpdateView: Bool = false @@ -59,28 +60,7 @@ public struct FXDatePickerView: View { HStack { - - Button(action: { - withAnimation { - arrowRotation = arrowRotation == 0 ? 90 : 0 - openShowSelectedMonths.toggle() - } - - }, label: { - - HStack(spacing: 5) { - - Text(getMonthName(from: displayedMonth)) - .toBold() - .foregroundColor(theme.main.monthTitle) - - Image(systemName: layoutDirection == .rightToLeft ? "chevron.left" : "chevron.right" ) - .font(.system(size: 12)) - .toBold() - .rotationEffect(.degrees(arrowRotation)) - .foregroundColor(theme.main.accentColor) - } - }) + monthTitleView() Spacer() @@ -136,7 +116,7 @@ public struct FXDatePickerView: View { } .id(shouldUpdateView) - .frame(height: openShowSelectedMonths != true ? 320 : 342) + .frame(height: openShowSelectedMonths != true ? (hideMarkers ? 280 : 320) : (hideMarkers ? 300 : 340)) .overlay( openShowSelectedMonths ? SelectMonthPickerView(selectedDate: $selectedDate, calendar: calendar, calenderType: calenderType) @@ -161,7 +141,7 @@ public struct FXDatePickerView: View { } } .padding() - .background(theme.main.backgroundColor) + .background(theme.main.backgroundStyle) .onAppear { setupCurrentDate() } @@ -175,18 +155,23 @@ public struct FXDatePickerView: View { public extension FXDatePickerView { - func hideMarkers(_ show: Bool = true) -> FXDatePickerView { + func hideMarkers(_ hide: Bool = true) -> FXDatePickerView { var fxDatePicker = self - fxDatePicker.hideMarkers = show + fxDatePicker.hideMarkers = hide return fxDatePicker } - func disableSwipe(_ hide: Bool = true) -> FXDatePickerView { + func disableSwipe(_ disable: Bool = true) -> FXDatePickerView { let fxDatePicker = self - fxDatePicker.disableSwipe = hide + fxDatePicker.disableSwipe = disable return fxDatePicker } + func hideDatePicker(_ hide: Bool = true) -> FXDatePickerView { + var fxDatePicker = self + fxDatePicker.hideDatePicker = hide + return fxDatePicker + } private func setupCurrentDate() { // load 6 years when DatePicker appear @@ -254,6 +239,36 @@ public extension FXDatePickerView { return formatter } -} - + @ViewBuilder func monthTitleView() -> some View { + if hideDatePicker { + Text(getMonthName(from: displayedMonth)) + .toBold() + .foregroundColor(theme.main.monthTitle) + } else { + + Button(action: { + withAnimation { + arrowRotation = arrowRotation == 0 ? 90 : 0 + openShowSelectedMonths.toggle() + } + + }, label: { + + HStack(spacing: 5) { + + Text(getMonthName(from: displayedMonth)) + .toBold() + .foregroundColor(theme.main.monthTitle) + + Image(systemName: layoutDirection == .rightToLeft ? "chevron.left" : "chevron.right" ) + .font(.system(size: 12)) + .toBold() + .rotationEffect(.degrees(arrowRotation)) + .foregroundColor(theme.main.accentColor) + } + }) + } + } + +} diff --git a/Sources/FXDatePicker/Views/SelectMonthPickerView.swift b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift index 28c881d..9093e2e 100644 --- a/Sources/FXDatePicker/Views/SelectMonthPickerView.swift +++ b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift @@ -1,5 +1,5 @@ //// -//SwiftUIView.swift +//SelectMonthPickerView.swift // // //Created by Basel Baragabah on 03/01/2024. @@ -63,10 +63,9 @@ struct SelectMonthPickerView: View { var body: some View { ZStack { - theme.main.backgroundColor - .ignoresSafeArea() + FXBackgroundView(background: theme.main.backgroundStyle) - PickerView(data: self.pickerData, + FXPickerView(data: self.pickerData, selections: self.$selections, textColor: UIColor(theme.main.monthTitle)) .onChange(of: selections) { value in From a71a87ce6dd094ed1e2abf53a682ccdd8b48906a Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Thu, 4 Jan 2024 11:35:01 +0300 Subject: [PATCH 10/21] Update README add hideDatePicker and backgroundStyle --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 77ef078..3f9578f 100644 --- a/README.md +++ b/README.md @@ -76,10 +76,37 @@ monthTitle: .white, daysName: Color(uiColor: UIColor(red: 0.45, green: 0.48, blue: 0.48, alpha: 1.00)), daysNumbers: Color(uiColor: UIColor(red: 0.83, green: 0.84, blue: 0.84, alpha: 1.00)), previousDaysNumber: Color(uiColor: UIColor(red: 0.44, green: 0.47, blue: 0.47, alpha: 1.00)), -backgroundColor: Color(uiColor: UIColor(red: 0.22, green: 0.25, blue: 0.25, alpha: 1.00))) +backgroundStyle: .color(Color(uiColor: UIColor(red: 0.22, green: 0.25, blue: 0.25, alpha: 1.00))) ) ``` -The default theme has a white background with a blue accent color. + `backgroundStyle` accept Color or any kind of Gradient for example using radialGradient as backgroundColor + +```swift +FXDatePickerView(selectedDate: $selectedDate, specialDates: specialDates) + .datePickerTheme(main: + .init( + accentColor: Color(UIColor(red: 0.82, green: 0.26, blue: 0.42, alpha: 1.00)), + monthTitle: Color(UIColor(red: 0.86, green: 0.87, blue: 0.88, alpha: 1.00)), + daysName: Color(UIColor(red: 0.80, green: 0.80, blue: 0.83, alpha: 1.00)), + daysNumbers: Color(UIColor(red: 0.83, green: 0.83, blue: 0.87, alpha: 1.00)), + previousDaysNumber: Color(UIColor(red: 0.66, green: 0.66, blue: 0.69, alpha: 1.00)), + backgroundStyle: + .radialGradient( + Gradient(colors: + [Color(UIColor(red: 0.19, green: 0.19, blue: 0.22, alpha: 1.00)), + Color(UIColor(red: 0.11, green: 0.11, blue: 0.14, alpha: 1.00)) + ] + ), + center: .center, + startRadius: 1, + endRadius: 300) + ) + ) + +``` + +The default theme has a white background with a blue accent color. Adjust the colors or gradients according to your preference for a personalized DatePicker appearance. + ### hideMarkers @@ -101,7 +128,7 @@ Here's how it looks with the hideMarkers() modifier applied: With this modifier, the FXDatePickerView will render without showing any markers associated with the specialDates. This keeps the calendar view simple and focused on basic date picking functionality. --- + ### disableSwipe @@ -117,6 +144,21 @@ FXDatePickerView(selectedDate: $selectedDate, specialDates: specialDates) In this example, the `FXDatePickerView` will no longer respond to swipe gestures for date navigation, and users will need to use buttons provided in the UI to change the selected date. +### hideDatePicker + +The `.hideDatePicker()` modifier is used with the `FXDatePickerView` to hide the change months/year using PickerView view tap on Month title. + +#### Example + +To apply this modifier, simply chain `.hideDatePicker()` to your `FXDatePickerView` instance. Here’s how you can do it: + +```swift +FXDatePickerView(selectedDate: $selectedDate, specialDates: specialDates) + .hideDatePicker() +``` +This modifier hides the UI elements responsible for changing months or years when tapping on the Month title within the FXDatePickerView. + + ## Installation ### [Swift Package Manager](https://swift.org/package-manager/) From 63110b5ddaf6d85fa658bc1b1a1ef94de2153280 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Thu, 4 Jan 2024 13:19:09 +0300 Subject: [PATCH 11/21] fix disableSwipe stop working after last version --- Sources/FXDatePicker/Views/FXDatePickerView.swift | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index a525315..7464684 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -18,7 +18,7 @@ public struct FXDatePickerView: View { @Environment(\.layoutDirection) private var layoutDirection private var hideMarkers: Bool = false - @State private var disableSwipe: Bool = false + private var disableSwipe: Bool = false private var hideDatePicker: Bool = false @State private var dateRange: [Date] = [] @@ -121,15 +121,8 @@ public struct FXDatePickerView: View { openShowSelectedMonths ? SelectMonthPickerView(selectedDate: $selectedDate, calendar: calendar, calenderType: calenderType) .onChange(of: selectedDate, perform: { value in - disableSwipe = true displayedMonth = value setupCurrentDate() - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { - disableSwipe = false - }) - - }) : nil @@ -162,7 +155,7 @@ public extension FXDatePickerView { } func disableSwipe(_ disable: Bool = true) -> FXDatePickerView { - let fxDatePicker = self + var fxDatePicker = self fxDatePicker.disableSwipe = disable return fxDatePicker } From 0f2db2420e122f9a6aa2d6b14ffbef032ec030e9 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Sun, 7 Jan 2024 14:02:05 +0300 Subject: [PATCH 12/21] add all type of hijri calenders --- .../FXDatePickerDemo/ContentView.swift | 8 ++++---- .../Modifiers/CalenderTypeModifier.swift | 9 ++++++++- Sources/FXDatePicker/UIKit/PickerView.swift | 20 +++++++++---------- Sources/FXDatePicker/Views/DayView.swift | 4 ++-- .../FXDatePicker/Views/FXBackgroundView.swift | 2 +- .../FXDatePicker/Views/FXDatePickerView.swift | 17 +++++++++++++--- Sources/FXDatePicker/Views/MonthView.swift | 4 ++-- .../Views/SelectMonthPickerView.swift | 2 +- Sources/FXDatePicker/Views/SwipeView.swift | 2 +- 9 files changed, 43 insertions(+), 25 deletions(-) diff --git a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift index 1ef07c6..36946a2 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift +++ b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift @@ -43,7 +43,7 @@ struct ContentView: View { var body: some View { VStack { - + Picker("", selection: $selectedCalender) { Text("Gregorian").tag(0) Text("Hijri").tag(1) @@ -53,15 +53,15 @@ struct ContentView: View { if value == 0 { calenderType = .gregorian } else { - calenderType = .hijri + calenderType = .hijri(.islamicUmmAlQura) } } switch calenderType { case .gregorian: FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) - //.hideMarkers() - //.hideDatePicker() + // .hideMarkers() + // .hideDatePicker() // .disableSwipe() .calenderType(calenderType) .datePickerTheme(main: diff --git a/Sources/FXDatePicker/Modifiers/CalenderTypeModifier.swift b/Sources/FXDatePicker/Modifiers/CalenderTypeModifier.swift index c4ed459..845929d 100644 --- a/Sources/FXDatePicker/Modifiers/CalenderTypeModifier.swift +++ b/Sources/FXDatePicker/Modifiers/CalenderTypeModifier.swift @@ -8,9 +8,16 @@ import SwiftUI +public enum HijriType { + case islamicUmmAlQura + case islamic + case islamicCivil + case islamicTabular +} + public enum CalenderType { case gregorian - case hijri + case hijri(HijriType) } public struct CalenderTypeModifier: ViewModifier { diff --git a/Sources/FXDatePicker/UIKit/PickerView.swift b/Sources/FXDatePicker/UIKit/PickerView.swift index 9bccef2..332604c 100644 --- a/Sources/FXDatePicker/UIKit/PickerView.swift +++ b/Sources/FXDatePicker/UIKit/PickerView.swift @@ -9,16 +9,16 @@ import SwiftUI -struct FXPickerView: UIViewRepresentable { +internal struct FXPickerView: UIViewRepresentable { var data: [[String]] @Binding var selections: [Int] var textColor: UIColor = .black - func makeCoordinator() -> FXPickerView.Coordinator { + internal func makeCoordinator() -> FXPickerView.Coordinator { Coordinator(self) } - func makeUIView(context: UIViewRepresentableContext) -> UIPickerView { + internal func makeUIView(context: UIViewRepresentableContext) -> UIPickerView { let picker = UIPickerView(frame: .zero) picker.dataSource = context.coordinator @@ -27,29 +27,29 @@ struct FXPickerView: UIViewRepresentable { return picker } - func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext) { + internal func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext) { for i in 0...(self.selections.count - 1) { view.selectRow(self.selections[i], inComponent: i, animated: false) } context.coordinator.parent = self } - class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate { + internal class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate { var parent: FXPickerView - init(_ pickerView: FXPickerView) { + internal init(_ pickerView: FXPickerView) { self.parent = pickerView } - func numberOfComponents(in FXPickerView: UIPickerView) -> Int { + internal func numberOfComponents(in FXPickerView: UIPickerView) -> Int { return self.parent.data.count } - func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + internal func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return self.parent.data[component].count } - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { + internal func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { var label: UILabel if let reuseLabel = view as? UILabel { label = reuseLabel @@ -65,7 +65,7 @@ struct FXPickerView: UIViewRepresentable { return label } - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + internal func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { self.parent.selections[component] = row } } diff --git a/Sources/FXDatePicker/Views/DayView.swift b/Sources/FXDatePicker/Views/DayView.swift index 8ca79b7..3c9c40a 100644 --- a/Sources/FXDatePicker/Views/DayView.swift +++ b/Sources/FXDatePicker/Views/DayView.swift @@ -9,7 +9,7 @@ import SwiftUI @available(iOS 14.0, *) -public struct DayView: View { +internal struct DayView: View { let date: Date? let isSelected: Bool let isBeforeToday: Bool @@ -23,7 +23,7 @@ public struct DayView: View { @Environment(\.calenderType) private var calenderType @Environment(\.datePickerTheme) private var theme - public var body: some View { + internal var body: some View { VStack(spacing: 0) { if let date = date { Text(dayFormatter.string(from: date)) diff --git a/Sources/FXDatePicker/Views/FXBackgroundView.swift b/Sources/FXDatePicker/Views/FXBackgroundView.swift index 148035a..4952a77 100644 --- a/Sources/FXDatePicker/Views/FXBackgroundView.swift +++ b/Sources/FXDatePicker/Views/FXBackgroundView.swift @@ -8,7 +8,7 @@ import SwiftUI - struct FXBackgroundView: View { +internal struct FXBackgroundView: View { let background: BackgroundStyle var body: some View { diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index 7464684..1970a44 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -38,15 +38,26 @@ public struct FXDatePickerView: View { case .gregorian: var calendar = Calendar(identifier: .gregorian) calendar.locale = Locale(identifier: Locale.preferredLanguages.first ?? "ar") - return calendar - case .hijri: - var calendar = Calendar(identifier: .islamicUmmAlQura) + case .hijri(let hijriType): + var calendar: Calendar + switch hijriType { + case .islamicUmmAlQura: + calendar = Calendar(identifier: .islamicUmmAlQura) + case .islamic: + calendar = Calendar(identifier: .islamic) + case .islamicCivil: + calendar = Calendar(identifier: .islamicCivil) + case .islamicTabular: + calendar = Calendar(identifier: .islamicTabular) + } calendar.locale = Locale(identifier: Locale.preferredLanguages.first ?? "ar") return calendar } } + + public init(selectedDate: Binding, specialDates: [SpecialDate]) { diff --git a/Sources/FXDatePicker/Views/MonthView.swift b/Sources/FXDatePicker/Views/MonthView.swift index 127ad65..0ad5ad4 100644 --- a/Sources/FXDatePicker/Views/MonthView.swift +++ b/Sources/FXDatePicker/Views/MonthView.swift @@ -9,7 +9,7 @@ import SwiftUI @available(iOS 14.0, *) -public struct MonthView: View { +internal struct MonthView: View { @Binding var displayedMonth: Date @Binding var selectedDate: Date let specialDates: [SpecialDate] @@ -55,7 +55,7 @@ public struct MonthView: View { private let maxRows: Int = 6 - public var body: some View { + internal var body: some View { let rowHeight: CGFloat = (hideMarkers == false) ? 50 : 45 let totalRowHeight = CGFloat(numberOfRows) * rowHeight let totalPaddingHeight = totalMonthViewHeight - totalRowHeight diff --git a/Sources/FXDatePicker/Views/SelectMonthPickerView.swift b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift index 9093e2e..f47c129 100644 --- a/Sources/FXDatePicker/Views/SelectMonthPickerView.swift +++ b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift @@ -8,7 +8,7 @@ import SwiftUI -struct SelectMonthPickerView: View { +internal struct SelectMonthPickerView: View { @Environment(\.datePickerTheme) private var theme let calenderType: CalenderType diff --git a/Sources/FXDatePicker/Views/SwipeView.swift b/Sources/FXDatePicker/Views/SwipeView.swift index f3b028a..e64e153 100644 --- a/Sources/FXDatePicker/Views/SwipeView.swift +++ b/Sources/FXDatePicker/Views/SwipeView.swift @@ -8,7 +8,7 @@ import SwiftUI -struct SwipeView: View { +internal struct SwipeView: View { let content: () -> Content var isDisable: Bool @Binding var displayedMonth: Date From bbd726e82fa017e5b8e6d219ebce5de28b7afc3a Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Sun, 7 Jan 2024 14:13:03 +0300 Subject: [PATCH 13/21] Update README.md --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f9578f..558351b 100644 --- a/README.md +++ b/README.md @@ -58,11 +58,27 @@ For a custom image, set dateType to .image and provide the date along with the n ```swift FXDatePickerView(selectedDate: $selectedDate, specialDates: specialDates) -.calenderType(.hijri) +.calenderType(.hijri(.islamicUmmAlQura)) ``` The default is `.gregorian.` +#### Hijri Calendar: + +For the Hijri calendar, you need to specify the type of calendar. The default in Saudi Arabia is .islamicUmmAlQura. However, you can choose from several types of Hijri calendars, such as .islamic, .islamicCivil, or .islamicTabular. + +Example setting a different Hijri calendar type: + +```swift +.calenderType(.hijri(.islamicCivil)) +``` + +#### Customization Note: +**Adapting to Local Moon Sighting**: The Hijri calendar is based on the lunar cycle, and its dates can vary based on moon sightings. This variation can sometimes necessitate adjusting the calendar by a day or so. + +**Flexibility for Users**: It's advisable to allow users to change the Hijri calendar type within your app. This flexibility can accommodate regional differences and personal preferences in moon sighting practices, ensuring that your app remains relevant and useful for a diverse user base. + + ### datePickerTheme Customize the theme of the DatePicker using datePickerTheme. For example: From d900e8d04ea72310e481f79a893836670a3f982e Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Mon, 8 Jan 2024 14:15:44 +0300 Subject: [PATCH 14/21] add limit range to swipe feature and changeMonth --- .../FXDatePicker/Views/FXDatePickerView.swift | 102 ++++++++++++------ 1 file changed, 70 insertions(+), 32 deletions(-) diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index 1970a44..614c285 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -53,11 +53,20 @@ public struct FXDatePickerView: View { calendar = Calendar(identifier: .islamicTabular) } calendar.locale = Locale(identifier: Locale.preferredLanguages.first ?? "ar") + return calendar } } - + private var canMoveToPreviousMonth: Bool { + guard let minDate = dateRange.first else { return false } + return calendar.date(byAdding: .month, value: -1, to: displayedMonth) ?? Date() >= minDate + } + + private var canMoveToNextMonth: Bool { + guard let maxDate = dateRange.last else { return false } + return calendar.date(byAdding: .month, value: 1, to: displayedMonth) ?? Date() <= maxDate + } public init(selectedDate: Binding, specialDates: [SpecialDate]) { @@ -80,15 +89,18 @@ public struct FXDatePickerView: View { Button(action: { changeMonth(by: -1) }) { Image(systemName: layoutDirection == .leftToRight ? "chevron.left" : "chevron.right") .toBold() - .foregroundColor(theme.main.accentColor) + .foregroundColor(canMoveToPreviousMonth ? theme.main.accentColor : Color.gray) + } .padding(.horizontal) - + .disabled(!canMoveToPreviousMonth) + Button(action: { changeMonth(by: 1) }) { Image(systemName: layoutDirection == .leftToRight ? "chevron.right" : "chevron.left") .toBold() - .foregroundColor(theme.main.accentColor) + .foregroundColor(canMoveToNextMonth ? theme.main.accentColor : Color.gray) } + .disabled(!canMoveToNextMonth) } } .padding(.horizontal, 12) @@ -150,7 +162,10 @@ public struct FXDatePickerView: View { setupCurrentDate() } .onChange(of: displayedMonth) { newMonth in - updateDateRangeIfNeeded(for: newMonth) + let start = Date() + let end = calendar.date(byAdding: .year, value: 1, to: start) ?? Date() + + updateDateRangeIfNeeded(for: newMonth, limitDate: end) } } @@ -178,39 +193,44 @@ public extension FXDatePickerView { } private func setupCurrentDate() { - // load 6 years when DatePicker appear - let startRange = calendar.date(byAdding: .year, value: -6, to: displayedMonth)! - let endRange = calendar.date(byAdding: .year, value: 6, to: displayedMonth)! - dateRange = generateMonths(start: startRange, end: endRange) + let start = displayedMonth + let end = calendar.date(byAdding: .year, value: 2, to: Date()) ?? Date() + dateRange = generateMonths(start: start, end: end) } - // Needed for Swipe feature to load more Months - private func updateDateRangeIfNeeded(for month: Date) { + private func updateDateRangeIfNeeded(for month: Date, limitDate: Date) { let monthStart = month - - // Check and extend the end of the range - if let lastMonth = dateRange.last, monthStart > calendar.date(byAdding: .month, value: -6, to: lastMonth)! { - let newEnd = calendar.date(byAdding: .month, value: 6, to: lastMonth)! - let newMonths = generateMonths(start: lastMonth, end: newEnd).filter { !dateRange.contains($0) } - dateRange.append(contentsOf: newMonths) - dateRange = Array(Set(dateRange)).sorted() // Remove duplicates and sort - print("Extended end of range. New range: \(dateRange)") - shouldUpdateView.toggle() - + + // Extend the end of the range but not beyond the limitDate + if let lastMonth = dateRange.last, + monthStart > calendar.date(byAdding: .month, value: -6, to: lastMonth) ?? Date(), + limitDate > lastMonth { + + let newEnd = min(calendar.date(byAdding: .month, value: 6, to: lastMonth) ?? Date(), limitDate) + extendDateRange(from: lastMonth, to: newEnd) } - - // Check and extend the start of the range - if let firstMonth = dateRange.first, monthStart < calendar.date(byAdding: .month, value: 6, to: firstMonth)! { - let newStart = calendar.date(byAdding: .month, value: -6, to: firstMonth)! - let newMonths = generateMonths(start: newStart, end: firstMonth).filter { !dateRange.contains($0) } + + // Extend the start of the range but not beyond the limitDate + if let firstMonth = dateRange.first, + monthStart < calendar.date(byAdding: .month, value: 6, to: firstMonth) ?? Date(), + limitDate < firstMonth { + + let newStart = max(calendar.date(byAdding: .month, value: -6, to: firstMonth) ?? Date(), limitDate) + extendDateRange(from: newStart, to: firstMonth, atStart: true) + } + } + + private func extendDateRange(from start: Date, to end: Date, atStart: Bool = false) { + let newMonths = generateMonths(start: start, end: end).filter { !dateRange.contains($0) } + if atStart { dateRange.insert(contentsOf: newMonths, at: 0) - dateRange = Array(Set(dateRange)).sorted() // Remove duplicates and sort - print("Extended start of range. New range: \(dateRange)") - shouldUpdateView.toggle() - + } else { + dateRange.append(contentsOf: newMonths) } + dateRange = Array(Set(dateRange)).sorted() + print("Extended date range. New range: \(dateRange)") } - + private func generateMonths(start: Date, end: Date) -> [Date] { var months: [Date] = [] @@ -224,11 +244,29 @@ public extension FXDatePickerView { return months } + func changeMonth(by increment: Int) { - if let newMonth = calendar.date(byAdding: .month, value: increment, to: displayedMonth) { + guard let newMonth = calendar.date(byAdding: .month, value: increment, to: displayedMonth) else { + print("Failed to calculate new month") + return + } + + // Assuming dateRange is sorted, get the minimum and maximum dates + guard let minDate = dateRange.first, let maxDate = dateRange.last else { + print("Date range is empty") + return + } + + // Check if the newMonth is within the dateRange + if newMonth >= minDate && newMonth <= maxDate { displayedMonth = newMonth + + } else { + print("New month is outside the date range") } } + + private func getMonthName(from date: Date) -> String { let formatter = monthFormatter From 962f798fd5401113794d9a0ddb47977f4d24766a Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Tue, 9 Jan 2024 13:40:28 +0300 Subject: [PATCH 15/21] add closeRange, PartialRangeFrom and PartialRangeThrough --- .../FXDatePickerDemo/ContentView.swift | 3 +- .../Modifiers/CalenderTypeModifier.swift | 26 -- Sources/FXDatePicker/UIKit/PickerView.swift | 18 +- Sources/FXDatePicker/Views/DayView.swift | 1 - .../FXDatePicker/Views/FXDatePickerView.swift | 227 ++++++++++++------ Sources/FXDatePicker/Views/MonthView.swift | 1 - .../Views/SelectMonthPickerView.swift | 116 ++++----- 7 files changed, 222 insertions(+), 170 deletions(-) diff --git a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift index 36946a2..d078da6 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift +++ b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift @@ -42,6 +42,7 @@ struct ContentView: View { var body: some View { + VStack { Picker("", selection: $selectedCalender) { @@ -59,7 +60,7 @@ struct ContentView: View { switch calenderType { case .gregorian: - FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) + FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates, in: Date()...) // .hideMarkers() // .hideDatePicker() // .disableSwipe() diff --git a/Sources/FXDatePicker/Modifiers/CalenderTypeModifier.swift b/Sources/FXDatePicker/Modifiers/CalenderTypeModifier.swift index 845929d..853222b 100644 --- a/Sources/FXDatePicker/Modifiers/CalenderTypeModifier.swift +++ b/Sources/FXDatePicker/Modifiers/CalenderTypeModifier.swift @@ -19,29 +19,3 @@ public enum CalenderType { case gregorian case hijri(HijriType) } - -public struct CalenderTypeModifier: ViewModifier { - var calenderType: CalenderType - - public func body(content: Content) -> some View { - content - .environment(\.calenderType, calenderType) - } -} - -public struct CalenderTypeKey: EnvironmentKey { - public static let defaultValue: CalenderType = .gregorian -} - -public extension EnvironmentValues { - var calenderType: CalenderType { - get { self[CalenderTypeKey.self] } - set { self[CalenderTypeKey.self] = newValue } - } -} - -public extension View { - func calenderType(_ type: CalenderType) -> some View { - self.modifier(CalenderTypeModifier(calenderType: type)) - } -} diff --git a/Sources/FXDatePicker/UIKit/PickerView.swift b/Sources/FXDatePicker/UIKit/PickerView.swift index 332604c..2c3894f 100644 --- a/Sources/FXDatePicker/UIKit/PickerView.swift +++ b/Sources/FXDatePicker/UIKit/PickerView.swift @@ -10,7 +10,7 @@ import SwiftUI internal struct FXPickerView: UIViewRepresentable { - var data: [[String]] + @Binding var data: [[String]] @Binding var selections: [Int] var textColor: UIColor = .black @@ -29,11 +29,23 @@ internal struct FXPickerView: UIViewRepresentable { internal func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext) { for i in 0...(self.selections.count - 1) { - view.selectRow(self.selections[i], inComponent: i, animated: false) + view.selectRow(self.selections[i], inComponent: i, animated: true) } + + DispatchQueue.main.async { + view.reloadAllComponents() // Reload components at the end + + // If only one month is available, select it automatically + if self.data.count > 1 && self.data[1].count == 1 { + self.selections[1] = 0 + view.selectRow(0, inComponent: 1, animated: false) + } + } + context.coordinator.parent = self } - + + internal class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate { var parent: FXPickerView diff --git a/Sources/FXDatePicker/Views/DayView.swift b/Sources/FXDatePicker/Views/DayView.swift index 3c9c40a..9232229 100644 --- a/Sources/FXDatePicker/Views/DayView.swift +++ b/Sources/FXDatePicker/Views/DayView.swift @@ -20,7 +20,6 @@ internal struct DayView: View { private let imageSize: CGFloat = 25 let calendar: Calendar - @Environment(\.calenderType) private var calenderType @Environment(\.datePickerTheme) private var theme internal var body: some View { diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index 614c285..39adee7 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -14,13 +14,13 @@ public struct FXDatePickerView: View { var specialDates: [SpecialDate] @Environment(\.datePickerTheme) private var theme - @Environment(\.calenderType) private var calenderType + private var calenderType: CalenderType = .gregorian @Environment(\.layoutDirection) private var layoutDirection private var hideMarkers: Bool = false private var disableSwipe: Bool = false private var hideDatePicker: Bool = false - + @State private var dateRange: [Date] = [] @State private var shouldUpdateView: Bool = false @@ -29,6 +29,8 @@ public struct FXDatePickerView: View { @State private var arrowRotation: Double = 0 + var closeRange: ClosedRange = Date()...Date() + private var daysOfWeek: [String] { return calendar.shortWeekdaySymbols } @@ -57,23 +59,110 @@ public struct FXDatePickerView: View { return calendar } } - + private var canMoveToPreviousMonth: Bool { - guard let minDate = dateRange.first else { return false } - return calendar.date(byAdding: .month, value: -1, to: displayedMonth) ?? Date() >= minDate - } - - private var canMoveToNextMonth: Bool { - guard let maxDate = dateRange.last else { return false } - return calendar.date(byAdding: .month, value: 1, to: displayedMonth) ?? Date() <= maxDate - } + guard let minDate = dateRange.first else { return false } + return calendar.date(byAdding: .month, value: -1, to: displayedMonth) ?? Date() >= minDate + } + private var canMoveToNextMonth: Bool { + guard let maxDate = dateRange.last else { return false } + return calendar.date(byAdding: .month, value: 1, to: displayedMonth) ?? Date() <= maxDate + } + // MARK:- with specialDates public init(selectedDate: Binding, specialDates: [SpecialDate]) { self._selectedDate = selectedDate self.specialDates = specialDates + + self.closeRange = Date.distantPast...Date.distantFuture + + self.displayedMonth = closeRange.lowerBound + } + + + public init(selectedDate: Binding, specialDates: [SpecialDate], in closeReange: ClosedRange) { + self._selectedDate = selectedDate + self.specialDates = specialDates + self.closeRange = closeReange + + self.displayedMonth = closeRange.lowerBound + } + + public init(selectedDate: Binding, specialDates: [SpecialDate], in range: PartialRangeFrom) { + self._selectedDate = selectedDate + self.specialDates = specialDates + + let end = calendar.date(byAdding: .year, value: 100, to: Date()) ?? Date() + + self.closeRange = range.lowerBound...end + + self.displayedMonth = closeRange.lowerBound + } + + public init(selectedDate: Binding, specialDates: [SpecialDate], in range: PartialRangeThrough) { + self._selectedDate = selectedDate + self.specialDates = specialDates + + let start = calendar.date(byAdding: .year, value: -100, to: Date()) ?? Date() + + self.closeRange = start...range.upperBound + + self.displayedMonth = closeRange.upperBound + } + + + // MARK:- without specialDates + public init(selectedDate: Binding) { + self._selectedDate = selectedDate + self.specialDates = [] + + let start = calendar.date(byAdding: .year, value: -100, to: Date()) ?? Date() + let end = calendar.date(byAdding: .year, value: 100, to: Date()) ?? Date() + + self.closeRange = start...end + + self.displayedMonth = closeRange.lowerBound + self.hideMarkers = true + } + + + public init(selectedDate: Binding, in closeReange: ClosedRange) { + self._selectedDate = selectedDate + self.specialDates = [] + self.closeRange = closeReange + + self.displayedMonth = closeRange.lowerBound + } + + public init(selectedDate: Binding, in range: PartialRangeFrom) { + self._selectedDate = selectedDate + self.specialDates = [] + + let end = calendar.date(byAdding: .year, value: 100, to: Date()) ?? Date() + + self.closeRange = range.lowerBound...end + + self.displayedMonth = closeRange.lowerBound + self.hideMarkers = true + + } + + public init(selectedDate: Binding, in range: PartialRangeThrough) { + self._selectedDate = selectedDate + self.specialDates = [] + + let start = calendar.date(byAdding: .year, value: -100, to: Date()) ?? Date() + + self.closeRange = start...range.upperBound + + self.displayedMonth = closeRange.upperBound + self.hideMarkers = true + } + + public var body: some View { VStack { @@ -81,7 +170,7 @@ public struct FXDatePickerView: View { HStack { monthTitleView() - + Spacer() if !openShowSelectedMonths { @@ -90,11 +179,11 @@ public struct FXDatePickerView: View { Image(systemName: layoutDirection == .leftToRight ? "chevron.left" : "chevron.right") .toBold() .foregroundColor(canMoveToPreviousMonth ? theme.main.accentColor : Color.gray) - + } .padding(.horizontal) .disabled(!canMoveToPreviousMonth) - + Button(action: { changeMonth(by: 1) }) { Image(systemName: layoutDirection == .leftToRight ? "chevron.right" : "chevron.left") .toBold() @@ -142,7 +231,7 @@ public struct FXDatePickerView: View { .frame(height: openShowSelectedMonths != true ? (hideMarkers ? 280 : 320) : (hideMarkers ? 300 : 340)) .overlay( - openShowSelectedMonths ? SelectMonthPickerView(selectedDate: $selectedDate, calendar: calendar, calenderType: calenderType) + openShowSelectedMonths ? SelectMonthPickerView(selectedDate: $selectedDate, calendar: calendar, calenderType: calenderType, closeRange: closeRange) .onChange(of: selectedDate, perform: { value in displayedMonth = value setupCurrentDate() @@ -162,10 +251,7 @@ public struct FXDatePickerView: View { setupCurrentDate() } .onChange(of: displayedMonth) { newMonth in - let start = Date() - let end = calendar.date(byAdding: .year, value: 1, to: start) ?? Date() - - updateDateRangeIfNeeded(for: newMonth, limitDate: end) + updateDateRangeIfNeeded(for: newMonth, limitDate: closeRange.upperBound) } } @@ -174,6 +260,12 @@ public struct FXDatePickerView: View { public extension FXDatePickerView { + func calenderType(_ calenderType: CalenderType = .gregorian) -> FXDatePickerView { + var fxDatePicker = self + fxDatePicker.calenderType = calenderType + return fxDatePicker + } + func hideMarkers(_ hide: Bool = true) -> FXDatePickerView { var fxDatePicker = self fxDatePicker.hideMarkers = hide @@ -193,33 +285,34 @@ public extension FXDatePickerView { } private func setupCurrentDate() { - let start = displayedMonth - let end = calendar.date(byAdding: .year, value: 2, to: Date()) ?? Date() - dateRange = generateMonths(start: start, end: end) + //displayedMonth = closeRange.lowerBound + dateRange = generateMonths(start: displayedMonth, end: closeRange.upperBound) } private func updateDateRangeIfNeeded(for month: Date, limitDate: Date) { let monthStart = month - + + let lastMonth = closeRange.upperBound + // Extend the end of the range but not beyond the limitDate - if let lastMonth = dateRange.last, - monthStart > calendar.date(byAdding: .month, value: -6, to: lastMonth) ?? Date(), + if monthStart > calendar.date(byAdding: .month, value: -6, to: lastMonth) ?? Date(), limitDate > lastMonth { - + let newEnd = min(calendar.date(byAdding: .month, value: 6, to: lastMonth) ?? Date(), limitDate) extendDateRange(from: lastMonth, to: newEnd) } - + + let firstMonth = closeRange.lowerBound + // Extend the start of the range but not beyond the limitDate - if let firstMonth = dateRange.first, - monthStart < calendar.date(byAdding: .month, value: 6, to: firstMonth) ?? Date(), + if monthStart < calendar.date(byAdding: .month, value: 6, to: firstMonth) ?? Date(), limitDate < firstMonth { - + let newStart = max(calendar.date(byAdding: .month, value: -6, to: firstMonth) ?? Date(), limitDate) extendDateRange(from: newStart, to: firstMonth, atStart: true) } } - + private func extendDateRange(from start: Date, to end: Date, atStart: Bool = false) { let newMonths = generateMonths(start: start, end: end).filter { !dateRange.contains($0) } if atStart { @@ -230,7 +323,7 @@ public extension FXDatePickerView { dateRange = Array(Set(dateRange)).sorted() print("Extended date range. New range: \(dateRange)") } - + private func generateMonths(start: Date, end: Date) -> [Date] { var months: [Date] = [] @@ -244,19 +337,19 @@ public extension FXDatePickerView { return months } - + func changeMonth(by increment: Int) { guard let newMonth = calendar.date(byAdding: .month, value: increment, to: displayedMonth) else { print("Failed to calculate new month") return } - + // Assuming dateRange is sorted, get the minimum and maximum dates guard let minDate = dateRange.first, let maxDate = dateRange.last else { print("Date range is empty") return } - + // Check if the newMonth is within the dateRange if newMonth >= minDate && newMonth <= maxDate { displayedMonth = newMonth @@ -265,8 +358,8 @@ public extension FXDatePickerView { print("New month is outside the date range") } } - - + + private func getMonthName(from date: Date) -> String { let formatter = monthFormatter @@ -281,36 +374,36 @@ public extension FXDatePickerView { return formatter } - @ViewBuilder func monthTitleView() -> some View { - if hideDatePicker { - Text(getMonthName(from: displayedMonth)) - .toBold() - .foregroundColor(theme.main.monthTitle) - } else { - - Button(action: { - withAnimation { - arrowRotation = arrowRotation == 0 ? 90 : 0 - openShowSelectedMonths.toggle() - } - - }, label: { - - HStack(spacing: 5) { - - Text(getMonthName(from: displayedMonth)) - .toBold() - .foregroundColor(theme.main.monthTitle) - - Image(systemName: layoutDirection == .rightToLeft ? "chevron.left" : "chevron.right" ) - .font(.system(size: 12)) - .toBold() - .rotationEffect(.degrees(arrowRotation)) - .foregroundColor(theme.main.accentColor) - } - }) - } - + @ViewBuilder func monthTitleView() -> some View { + if hideDatePicker { + Text(getMonthName(from: displayedMonth)) + .toBold() + .foregroundColor(theme.main.monthTitle) + } else { + + Button(action: { + withAnimation { + arrowRotation = arrowRotation == 0 ? 90 : 0 + openShowSelectedMonths.toggle() + } + + }, label: { + + HStack(spacing: 5) { + + Text(getMonthName(from: displayedMonth)) + .toBold() + .foregroundColor(theme.main.monthTitle) + + Image(systemName: layoutDirection == .rightToLeft ? "chevron.left" : "chevron.right" ) + .font(.system(size: 12)) + .toBold() + .rotationEffect(.degrees(arrowRotation)) + .foregroundColor(theme.main.accentColor) + } + }) + } + } } diff --git a/Sources/FXDatePicker/Views/MonthView.swift b/Sources/FXDatePicker/Views/MonthView.swift index 0ad5ad4..d320f70 100644 --- a/Sources/FXDatePicker/Views/MonthView.swift +++ b/Sources/FXDatePicker/Views/MonthView.swift @@ -15,7 +15,6 @@ internal struct MonthView: View { let specialDates: [SpecialDate] @Environment(\.datePickerTheme) private var theme - @Environment(\.calenderType) private var calenderType @Environment(\.layoutDirection) private var layoutDirection let calendar: Calendar diff --git a/Sources/FXDatePicker/Views/SelectMonthPickerView.swift b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift index f47c129..593ceaa 100644 --- a/Sources/FXDatePicker/Views/SelectMonthPickerView.swift +++ b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift @@ -9,106 +9,80 @@ import SwiftUI internal struct SelectMonthPickerView: View { - @Environment(\.datePickerTheme) private var theme - let calenderType: CalenderType - let calendar: Calendar + let calenderType: CalenderType + let closeRange: ClosedRange @Binding var selectedDate: Date - - private var years: [Int] { - switch calenderType { - case .hijri: - return Array(1300...1500) - default: - return Array(1900...2100) - } - } - - private var numberFormatter: NumberFormatter { - let formatter = NumberFormatter() - let currentLocale = Locale.current - - if currentLocale.languageCode == "ar" { - formatter.locale = Locale(identifier: "ar") - } else { - formatter.locale = Locale(identifier: "en") - } - - return formatter - } - - private var months: [String] - private var pickerData: [[String]] = [[]] + @State private var pickerData: [[String]] = [[]] @State private var selections: [Int] = [] - init(selectedDate: Binding, calendar: Calendar, calenderType: CalenderType) { + private var years: [Int] { + let startYear = calendar.component(.year, from: closeRange.lowerBound) + let endYear = calendar.component(.year, from: closeRange.upperBound) + return Array(startYear...endYear) + } + + init(selectedDate: Binding, calendar: Calendar, calenderType: CalenderType, closeRange: ClosedRange) { self.calendar = calendar self._selectedDate = selectedDate - self.months = calendar.monthSymbols self.calenderType = calenderType + self.closeRange = closeRange let currentYear = calendar.component(.year, from: selectedDate.wrappedValue) let currentMonth = calendar.component(.month, from: selectedDate.wrappedValue) - 1 - - let yearStrings = years.map { numberFormatter.string(from: NSNumber(value: $0)) ?? "" } - let monthStrings = months.map { String($0) } - pickerData = [yearStrings, monthStrings] - let yearIndex = years.firstIndex(of: currentYear) ?? 0 + + let initialMonths = getMonths(for: currentYear) + self._pickerData = State(initialValue: [years.map { String($0) }, initialMonths]) self._selections = State(initialValue: [yearIndex, currentMonth]) } - var body: some View { ZStack { FXBackgroundView(background: theme.main.backgroundStyle) - - FXPickerView(data: self.pickerData, - selections: self.$selections, - textColor: UIColor(theme.main.monthTitle)) - .onChange(of: selections) { value in - updateSelectedDate(with: value) - } + + FXPickerView(data: $pickerData, selections: $selections, textColor: UIColor(theme.main.monthTitle)) + .onChange(of: selections[0]) { yearIndex in + let selectedYear = years[yearIndex] + pickerData[1] = getMonths(for: selectedYear) + } + .onChange(of: selections) { value in + updateSelectedDate(with: value) + } } - - } -} -extension SelectMonthPickerView { - - func updateSelectedDate(with selections: [Int]) { - var components = DateComponents() - - let yearIndex = selections[0] - let monthIndex = selections[1] - let dayIndex = calendar.component(.day, from: selectedDate) - - components.year = years[yearIndex] - components.month = monthIndex + 1 - - let newDate = calendar.date(from: components) + private func getMonths(for year: Int) -> [String] { + let isStartYear = year == calendar.component(.year, from: closeRange.lowerBound) + let isEndYear = year == calendar.component(.year, from: closeRange.upperBound) - let newMonthRange = calendar.range(of: .day, in: .month, for: newDate!) - - // If the selected day does exist in the new month, it will be the default day of the month - if dayIndex <= newMonthRange!.count { - components.day = dayIndex + if isStartYear { + let firstMonth = calendar.component(.month, from: closeRange.lowerBound) + return Array(calendar.monthSymbols.dropFirst(firstMonth - 1)) + } else if isEndYear { + let lastMonth = calendar.component(.month, from: closeRange.upperBound) + return Array(calendar.monthSymbols.prefix(lastMonth)) } else { - // If the selected day doesn't exist in the new month, default will move to the first day of the month - components.day = 1 + return calendar.monthSymbols } - - if let updatedDate = calendar.date(from: components) { + } + + func updateSelectedDate(with selections: [Int]) { + var components = DateComponents() + components.year = years[selections[0]] + components.month = selections[1] + 1 + components.day = calendar.component(.day, from: selectedDate) + + if let updatedDate = calendar.date(from: components), closeRange.contains(updatedDate) { selectedDate = updatedDate - } else { - print("Failed to create date") } } } + + #Preview { - SelectMonthPickerView(selectedDate: .constant(Date()), calendar: Calendar.current, calenderType: .gregorian) + SelectMonthPickerView(selectedDate: .constant(Date()), calendar: Calendar.current, calenderType: .gregorian, closeRange: Date()...Date()) } From af0cf96b6c6f14d2a04792009fb254648150f2ed Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Thu, 11 Jan 2024 11:01:36 +0300 Subject: [PATCH 16/21] add range --- .../FXDatePickerDemo/ContentView.swift | 45 +++-- .../FXDatePickerDemo/Localizable.xcstrings | 5 +- .../Healper/findCurrentDateIndex.swift | 19 ++ Sources/FXDatePicker/Views/DayView.swift | 41 ++++- .../FXDatePicker/Views/FXDatePickerView.swift | 167 +++++++----------- Sources/FXDatePicker/Views/MonthView.swift | 2 + Sources/FXDatePicker/Views/SwipeView.swift | 21 ++- 7 files changed, 173 insertions(+), 127 deletions(-) create mode 100644 Sources/FXDatePicker/Healper/findCurrentDateIndex.swift diff --git a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift index d078da6..5cc01fe 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift +++ b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift @@ -40,30 +40,45 @@ struct ContentView: View { @State private var selectedCalender = 0 @State private var calenderType: CalenderType = .gregorian + private var cloaseRange: ClosedRange { + let calendar = Calendar.current + // Set 'today' to the start of the current day + let startOfToday = calendar.startOfDay(for: Date()) + + guard let endOfRange = calendar.date(bySetting: .day, value: 18, of: startOfToday) else { + fatalError("Could not create start or end date for range") + } + + return startOfToday...endOfRange + } + + var body: some View { VStack { - Picker("", selection: $selectedCalender) { - Text("Gregorian").tag(0) - Text("Hijri").tag(1) - } - .pickerStyle(.segmented) - .onChange(of: selectedCalender) { value in - if value == 0 { - calenderType = .gregorian - } else { - calenderType = .hijri(.islamicUmmAlQura) - } - } +// Picker("", selection: $selectedCalender) { +// Text("Gregorian").tag(0) +// Text("Hijri").tag(1) +// } +// .pickerStyle(.segmented) +// .onChange(of: selectedCalender) { value in +// if value == 0 { +// calenderType = .gregorian +// } else { +// calenderType = .hijri(.islamicUmmAlQura) +// } +// } switch calenderType { case .gregorian: - FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates, in: Date()...) +// FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) + + FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates, in: cloaseRange) // .hideMarkers() // .hideDatePicker() - // .disableSwipe() +// .disableSwipe() .calenderType(calenderType) .datePickerTheme(main: .init( @@ -89,7 +104,7 @@ struct ContentView: View { FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) .hideMarkers() - .disableSwipe() + // .disableSwipe() .calenderType(calenderType) .datePickerTheme(main: .init( diff --git a/FXDatePickerDemo/FXDatePickerDemo/Localizable.xcstrings b/FXDatePickerDemo/FXDatePickerDemo/Localizable.xcstrings index 49f81d6..b6a14d0 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/Localizable.xcstrings +++ b/FXDatePickerDemo/FXDatePickerDemo/Localizable.xcstrings @@ -1,10 +1,8 @@ { "sourceLanguage" : "en", "strings" : { - "" : { - - }, "Gregorian" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -15,6 +13,7 @@ } }, "Hijri" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { diff --git a/Sources/FXDatePicker/Healper/findCurrentDateIndex.swift b/Sources/FXDatePicker/Healper/findCurrentDateIndex.swift new file mode 100644 index 0000000..c86cd8e --- /dev/null +++ b/Sources/FXDatePicker/Healper/findCurrentDateIndex.swift @@ -0,0 +1,19 @@ +//// +//File.swift +// +// +//Created by Basel Baragabah on 11/01/2024. +//Copyright © 2024 Basel Baragabah. All rights reserved. +// + +import Foundation + +public func findCurrentDateIndex(calendar: Calendar, dateRange: [Date], selectedDate: Date?) -> Int { + // Find the index of the current date + + guard let selectedDate = selectedDate else {return 0} + let currentDate = calendar.startOfDay(for: selectedDate) + return dateRange.firstIndex(where: { calendar.isDate($0, equalTo: currentDate, toGranularity: .day) }) ?? (dateRange.count - 1) + } + + diff --git a/Sources/FXDatePicker/Views/DayView.swift b/Sources/FXDatePicker/Views/DayView.swift index 9232229..0ec1b45 100644 --- a/Sources/FXDatePicker/Views/DayView.swift +++ b/Sources/FXDatePicker/Views/DayView.swift @@ -11,32 +11,35 @@ import SwiftUI @available(iOS 14.0, *) internal struct DayView: View { let date: Date? + let closeRange: ClosedRange let isSelected: Bool let isBeforeToday: Bool let isToday: Bool let specialDate: SpecialDate? let hideMarkers: Bool - + private let imageSize: CGFloat = 25 let calendar: Calendar @Environment(\.datePickerTheme) private var theme + private var isInRange: Bool { + guard let date = date else { return false } + return closeRange.contains(date) + } + internal var body: some View { VStack(spacing: 0) { if let date = date { Text(dayFormatter.string(from: date)) - .foregroundColor(isSelected ? - (isToday ? .white : theme.main.accentColor) : - (isBeforeToday ? theme.main.previousDaysNumber : - (isToday ? theme.main.accentColor : theme.main.daysNumbers) - )) + .foregroundColor(calculateTextColor()) .font(.system(size: 20)) .toBold(isSelected || isToday) .frame(width: 30, height: 30) .padding(.horizontal, 8) .background(isSelected ? isToday ? theme.main.accentColor : theme.main.accentColor.opacity(0.2) : Color.clear) .cornerRadius(20) + .allowsHitTesting(isInRange) } if !hideMarkers { @@ -75,6 +78,32 @@ extension DayView { } } +// private func calculateTextColor() -> Color { +// if !isInRange { +// return Color.gray +// } else if isSelected { +// return isToday ? .white : theme.main.accentColor +// } else { +// return isBeforeToday ? theme.main.previousDaysNumber : (isToday ? theme.main.accentColor : theme.main.daysNumbers) +// } +// } + + private func calculateTextColor() -> Color { + if !isInRange { + // Gray out the day if it's not in range + return Color.gray + } else if isSelected { + // Selected day color + return isToday ? .white : theme.main.accentColor + } else if isToday { + // Today's color if not selected + return theme.main.accentColor + } else { + // Normal day color + return theme.main.daysNumbers + } + } + private var dayFormatter: DateFormatter { let formatter = DateFormatter() diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index 39adee7..0ef424a 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -22,12 +22,11 @@ public struct FXDatePickerView: View { private var hideDatePicker: Bool = false @State private var dateRange: [Date] = [] - - @State private var shouldUpdateView: Bool = false - + @State private var openShowSelectedMonths: Bool = false @State private var arrowRotation: Double = 0 + @State private var selectedIndex: Int = 0 var closeRange: ClosedRange = Date()...Date() @@ -39,6 +38,7 @@ public struct FXDatePickerView: View { switch calenderType { case .gregorian: var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .current calendar.locale = Locale(identifier: Locale.preferredLanguages.first ?? "ar") return calendar @@ -61,23 +61,26 @@ public struct FXDatePickerView: View { } private var canMoveToPreviousMonth: Bool { - guard let minDate = dateRange.first else { return false } - return calendar.date(byAdding: .month, value: -1, to: displayedMonth) ?? Date() >= minDate + guard let previousMonth = calendar.date(byAdding: .month, value: -1, to: displayedMonth) else { return false } + return previousMonth >= closeRange.lowerBound } - + private var canMoveToNextMonth: Bool { - guard let maxDate = dateRange.last else { return false } - return calendar.date(byAdding: .month, value: 1, to: displayedMonth) ?? Date() <= maxDate + guard let nextMonth = calendar.date(byAdding: .month, value: 1, to: displayedMonth) else { return false } + return nextMonth <= closeRange.upperBound } - + // MARK:- with specialDates public init(selectedDate: Binding, specialDates: [SpecialDate]) { self._selectedDate = selectedDate self.specialDates = specialDates - self.closeRange = Date.distantPast...Date.distantFuture - - self.displayedMonth = closeRange.lowerBound + let start = calendar.date(byAdding: .year, value: -50, to: Date()) ?? Date() + let end = calendar.date(byAdding: .year, value: 50, to: Date()) ?? Date() + + self.closeRange = start...end + + self._displayedMonth = State(initialValue: Date()) } @@ -86,29 +89,30 @@ public struct FXDatePickerView: View { self.specialDates = specialDates self.closeRange = closeReange - self.displayedMonth = closeRange.lowerBound + self._displayedMonth = State(initialValue: closeRange.lowerBound) } public init(selectedDate: Binding, specialDates: [SpecialDate], in range: PartialRangeFrom) { self._selectedDate = selectedDate self.specialDates = specialDates - let end = calendar.date(byAdding: .year, value: 100, to: Date()) ?? Date() + let startOfToday = calendar.startOfDay(for: Date()) + let end = calendar.date(byAdding: .year, value: 50, to: Date()) ?? Date() - self.closeRange = range.lowerBound...end + self.closeRange = startOfToday...end - self.displayedMonth = closeRange.lowerBound + self._displayedMonth = State(initialValue: startOfToday) } public init(selectedDate: Binding, specialDates: [SpecialDate], in range: PartialRangeThrough) { self._selectedDate = selectedDate self.specialDates = specialDates - let start = calendar.date(byAdding: .year, value: -100, to: Date()) ?? Date() + let start = calendar.date(byAdding: .year, value: -50, to: Date()) ?? Date() - self.closeRange = start...range.upperBound + self.closeRange = start...range.upperBound + 1 - self.displayedMonth = closeRange.upperBound + self._displayedMonth = State(initialValue: range.upperBound) } @@ -117,12 +121,12 @@ public struct FXDatePickerView: View { self._selectedDate = selectedDate self.specialDates = [] - let start = calendar.date(byAdding: .year, value: -100, to: Date()) ?? Date() - let end = calendar.date(byAdding: .year, value: 100, to: Date()) ?? Date() + let start = calendar.date(byAdding: .year, value: -50, to: Date()) ?? Date() + let end = calendar.date(byAdding: .year, value: 50, to: Date()) ?? Date() self.closeRange = start...end - self.displayedMonth = closeRange.lowerBound + self._displayedMonth = State(initialValue: Date()) self.hideMarkers = true } @@ -132,18 +136,19 @@ public struct FXDatePickerView: View { self.specialDates = [] self.closeRange = closeReange - self.displayedMonth = closeRange.lowerBound + self._displayedMonth = State(initialValue: closeRange.lowerBound) } public init(selectedDate: Binding, in range: PartialRangeFrom) { self._selectedDate = selectedDate self.specialDates = [] - let end = calendar.date(byAdding: .year, value: 100, to: Date()) ?? Date() - - self.closeRange = range.lowerBound...end - - self.displayedMonth = closeRange.lowerBound + let startOfToday = calendar.startOfDay(for: Date()) + let end = calendar.date(byAdding: .year, value: 50, to: Date()) ?? Date() + + self.closeRange = startOfToday...end + + self._displayedMonth = State(initialValue: closeRange.lowerBound) self.hideMarkers = true } @@ -152,17 +157,16 @@ public struct FXDatePickerView: View { self._selectedDate = selectedDate self.specialDates = [] - let start = calendar.date(byAdding: .year, value: -100, to: Date()) ?? Date() + let start = calendar.date(byAdding: .year, value: -50, to: Date()) ?? Date() - self.closeRange = start...range.upperBound + self.closeRange = start...(range.upperBound + 1) - self.displayedMonth = closeRange.upperBound + self._displayedMonth = State(initialValue: closeRange.upperBound) self.hideMarkers = true } - public var body: some View { VStack { @@ -207,34 +211,40 @@ public struct FXDatePickerView: View { } } - SwipeView(dateRange: $dateRange, displayedMonth: $displayedMonth, isDisable: disableSwipe) { + + SwipeView(dateRange: $dateRange, displayedMonth: $displayedMonth, selectedIndex: $selectedIndex, calendar: calendar, isDisable: disableSwipe) { if disableSwipe { MonthView(displayedMonth: $displayedMonth, selectedDate: $selectedDate, specialDates: specialDates, calendar: calendar, - hideMarkers: hideMarkers) + hideMarkers: hideMarkers, + closeRange: closeRange) } else { - ForEach($dateRange, id: \.self) { $month in - MonthView(displayedMonth: $month, + // ForEach($dateRange, id: \.self) { $month in + ForEach(0.. Int { + // Find the index of the current date + return dateRange.firstIndex(where: { calendar.isDate($0, equalTo: currentDate, toGranularity: .day) }) ?? (dateRange.count - 1) + } } +//MARK:- Modifiers public extension FXDatePickerView { - func calenderType(_ calenderType: CalenderType = .gregorian) -> FXDatePickerView { var fxDatePicker = self fxDatePicker.calenderType = calenderType @@ -283,48 +293,15 @@ public extension FXDatePickerView { fxDatePicker.hideDatePicker = hide return fxDatePicker } +} + +//MARK:- Healper Functions +public extension FXDatePickerView { private func setupCurrentDate() { - //displayedMonth = closeRange.lowerBound - dateRange = generateMonths(start: displayedMonth, end: closeRange.upperBound) - } - - private func updateDateRangeIfNeeded(for month: Date, limitDate: Date) { - let monthStart = month - - let lastMonth = closeRange.upperBound - - // Extend the end of the range but not beyond the limitDate - if monthStart > calendar.date(byAdding: .month, value: -6, to: lastMonth) ?? Date(), - limitDate > lastMonth { - - let newEnd = min(calendar.date(byAdding: .month, value: 6, to: lastMonth) ?? Date(), limitDate) - extendDateRange(from: lastMonth, to: newEnd) - } - - let firstMonth = closeRange.lowerBound - - // Extend the start of the range but not beyond the limitDate - if monthStart < calendar.date(byAdding: .month, value: 6, to: firstMonth) ?? Date(), - limitDate < firstMonth { - - let newStart = max(calendar.date(byAdding: .month, value: -6, to: firstMonth) ?? Date(), limitDate) - extendDateRange(from: newStart, to: firstMonth, atStart: true) - } - } - - private func extendDateRange(from start: Date, to end: Date, atStart: Bool = false) { - let newMonths = generateMonths(start: start, end: end).filter { !dateRange.contains($0) } - if atStart { - dateRange.insert(contentsOf: newMonths, at: 0) - } else { - dateRange.append(contentsOf: newMonths) - } - dateRange = Array(Set(dateRange)).sorted() - print("Extended date range. New range: \(dateRange)") + dateRange = generateMonths(start: closeRange.lowerBound, end: closeRange.upperBound) } - - + private func generateMonths(start: Date, end: Date) -> [Date] { var months: [Date] = [] var currentDate = start @@ -336,31 +313,21 @@ public extension FXDatePickerView { return months } - - + func changeMonth(by increment: Int) { - guard let newMonth = calendar.date(byAdding: .month, value: increment, to: displayedMonth) else { + guard let newMonth = calendar.date(byAdding: .month, value: increment, to: displayedMonth), + let newMonthStart = calendar.date(from: calendar.dateComponents([.year, .month], from: newMonth)) else { print("Failed to calculate new month") return } - // Assuming dateRange is sorted, get the minimum and maximum dates - guard let minDate = dateRange.first, let maxDate = dateRange.last else { - print("Date range is empty") - return - } - - // Check if the newMonth is within the dateRange - if newMonth >= minDate && newMonth <= maxDate { + if closeRange.contains(newMonthStart) { displayedMonth = newMonth - } else { - print("New month is outside the date range") + print("New month is outside the close range") } } - - - + private func getMonthName(from date: Date) -> String { let formatter = monthFormatter return formatter.string(from: date) diff --git a/Sources/FXDatePicker/Views/MonthView.swift b/Sources/FXDatePicker/Views/MonthView.swift index d320f70..0f76bb3 100644 --- a/Sources/FXDatePicker/Views/MonthView.swift +++ b/Sources/FXDatePicker/Views/MonthView.swift @@ -19,6 +19,7 @@ internal struct MonthView: View { let calendar: Calendar let hideMarkers: Bool + let closeRange: ClosedRange private var totalCells: Int { 6 * 7 } // 6 rows, 7 columns @@ -78,6 +79,7 @@ extension MonthView { let specialDate = findSpecialDate(for: date) DayView(date: date, + closeRange: closeRange, isSelected: isSameDay(date1: selectedDate, date2: date), isBeforeToday: isDateBeforeToday(date: date), isToday: isToday(date: date), diff --git a/Sources/FXDatePicker/Views/SwipeView.swift b/Sources/FXDatePicker/Views/SwipeView.swift index e64e153..9185101 100644 --- a/Sources/FXDatePicker/Views/SwipeView.swift +++ b/Sources/FXDatePicker/Views/SwipeView.swift @@ -11,28 +11,43 @@ import SwiftUI internal struct SwipeView: View { let content: () -> Content var isDisable: Bool + var calendar: Calendar + @Binding var displayedMonth: Date @Binding var dateRange: [Date] + @Binding var selectedIndex: Int - init(dateRange: Binding<[Date]> ,displayedMonth: Binding, isDisable: Bool, @ViewBuilder content: @escaping () -> Content) { + init(dateRange: Binding<[Date]> ,displayedMonth: Binding, selectedIndex: Binding , calendar: Calendar, isDisable: Bool, @ViewBuilder content: @escaping () -> Content) { self._dateRange = dateRange self._displayedMonth = displayedMonth self.isDisable = isDisable self.content = content + self.calendar = calendar + self._selectedIndex = selectedIndex + } var body: some View { Group { if isDisable == false { - TabView(selection: $displayedMonth) { + TabView(selection: $selectedIndex) { content() } .tabViewStyle(.page(indexDisplayMode: .never)) - + .onAppear { + let selectedDate = calendar.startOfDay(for: Date()) + print("selectedDate: \(selectedDate)") + selectedIndex = findCurrentDateIndex(calendar: calendar, dateRange: dateRange, selectedDate: selectedDate) + } + .onChange(of: selectedIndex) { newValue in + displayedMonth = dateRange[newValue] + } } else { content() } } } + } + From 4ec8eaf9d2d8e275b218bea925eab821a2322527 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Thu, 11 Jan 2024 11:19:21 +0300 Subject: [PATCH 17/21] Update README - Add Range Section --- README.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 558351b..21a11c9 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,79 @@ * **Flexibility**: FXDatePicker is ideal for creating distinctive, engaging date-picking experiences. The iOS DatePicker is more suited for applications that require basic date selection. # Usage -1. Add a binding `Date` variable to save the selection. -2. Create a `specialDates` array to add images below dates. -3. Initialize FXDatePicker and present it as desired. + +### Step 1: Add a Binding `Date` Variable +Create a `Date` variable in your Swift file to save the date selection from the picker. + +```swift +@State private var selectedDate: Date = Date() +``` + +### Step 2: Configure specialDates (Optional) +Create an array of specialDates if you want to display images on specific dates. This step is optional. If you prefer a standard calendar without special dates, you can skip this. + +```swift +let specialDates: [Date: UIImage] = [ + // Your special dates and images here +] +``` + +### Step 3: Initialize FXDatePicker +Add the FXDatePicker to your view. You can use it with or without specialDates. + +Example using specialDates: ```swift FXDatePickerView(selectedDate: $selectedDate, specialDates: specialDates) ``` +Example without specialDates: +```swift +FXDatePickerView(selectedDate: $selectedDate) +``` + +### Range +`FXDatePicker` also supports selecting dates within specified ranges. This can be particularly useful for applications where you need to restrict date selection to a certain period. Below are examples of how to use `FXDatePicker` with different types of date ranges. + + +#### Using ClosedRange +To use a closed range (a range with both a start and an end date), first create a `ClosedRange` variable. For example, to create a range for the current month: + + +```swift +private var closeRange: ClosedRange { + let calendar = Calendar.current + let startOfToday = calendar.startOfDay(for: Date()) + + guard let endOfRange = calendar.date(bySetting: .day, value: 18, of: startOfToday) else { + fatalError("Could not create start or end date for range") + } + + return startOfToday...endOfRange +} +``` + +Then, initialize `FXDatePicker` with this range: + + +```swift +FXDatePickerView(selectedDate: $selectedDate, specialDates: specialDates, in: cloaseRange) +``` + +#### Using PartialRangeFrom +For a range starting from a specific date and extending indefinitely into the future: + +```swift +FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates, in: Date()...) +``` + +### Using PartialRangeThrough +For a range that starts at any time in the past and ends at a specific date: + +```swift +FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates, in: ...Date()) +``` + ## Using `specialDates` to Add Custom Markers The `specialDates` array allows you to add custom markers to specific dates in your calendar. You can use either custom images or SF Symbols to highlight these dates. Each special date is defined with its type (`SpecialDateType`), which can be an `ImageType` for images or an `SFSymbolsType` for SF Symbols. Format the dates in the "Day/Month/Year" format. From bb7050633b76eacb19702ee552c9c71063df6aa4 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Thu, 11 Jan 2024 11:21:21 +0300 Subject: [PATCH 18/21] Add Range Section --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 21a11c9..a4c90e5 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,10 @@ FXDatePickerView(selectedDate: $selectedDate) ``` ### Range + +![range](https://github.com/X901/FXDatePicker/assets/16876982/d2948ef8-7a13-493e-92d7-4847dccdc8a9) + + `FXDatePicker` also supports selecting dates within specified ranges. This can be particularly useful for applications where you need to restrict date selection to a certain period. Below are examples of how to use `FXDatePicker` with different types of date ranges. From c86258b3a94cb5231feb823165760a7e2cdfeb3a Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Thu, 11 Jan 2024 13:06:17 +0300 Subject: [PATCH 19/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a4c90e5..cdabb48 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Create a `Date` variable in your Swift file to save the date selection from the Create an array of specialDates if you want to display images on specific dates. This step is optional. If you prefer a standard calendar without special dates, you can skip this. ```swift -let specialDates: [Date: UIImage] = [ +let specialDates: [SpecialDate] = [ // Your special dates and images here ] ``` From 3d43c45b97235e8146db9d2bc7eebb999ea29bd4 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Thu, 11 Jan 2024 13:20:31 +0300 Subject: [PATCH 20/21] Create FUNDING.yml --- .github/FUNDING.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..b4349b7 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,14 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: ["https://www.paypal.me/BASILBARAGABH"] + From 1ea6231478be69f62974c7b2fbeb9e6f1e9cefa5 Mon Sep 17 00:00:00 2001 From: Basel Baragabah Date: Thu, 11 Jan 2024 23:18:38 +0300 Subject: [PATCH 21/21] remove unused code --- .../FXDatePickerDemo/ContentView.swift | 34 +++++++------- .../FXDatePickerDemo/Localizable.xcstrings | 5 ++- Sources/FXDatePicker/Data/SpecialDate.swift | 4 +- .../FXDatePicker/Extensions/Bold+Ext.swift | 2 +- .../Extensions/Calendar+Ext.swift | 18 ++++++++ .../FXDatePicker/Extensions/View+Ext.swift | 8 ++-- .../Healper/findCurrentDateIndex.swift | 19 -------- .../Modifiers/ThemeColorModifier.swift | 4 +- Sources/FXDatePicker/UIKit/PickerView.swift | 20 ++++----- Sources/FXDatePicker/Views/DayView.swift | 28 ++++-------- .../FXDatePicker/Views/FXDatePickerView.swift | 45 +++++++++---------- Sources/FXDatePicker/Views/MonthView.swift | 27 +++++------ .../Views/SelectMonthPickerView.swift | 22 ++++----- Sources/FXDatePicker/Views/SwipeView.swift | 11 +++-- 14 files changed, 112 insertions(+), 135 deletions(-) create mode 100644 Sources/FXDatePicker/Extensions/Calendar+Ext.swift delete mode 100644 Sources/FXDatePicker/Healper/findCurrentDateIndex.swift diff --git a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift index 5cc01fe..600eae2 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift +++ b/FXDatePickerDemo/FXDatePickerDemo/ContentView.swift @@ -13,7 +13,7 @@ struct ContentView: View { init() { // Change the app language to Arabic - UserDefaults.standard.set(["en"], forKey: "AppleLanguages") +// UserDefaults.standard.set(["ar"], forKey: "AppleLanguages") } @State private var selectedGregorianDate = Date() @@ -58,27 +58,27 @@ struct ContentView: View { VStack { -// Picker("", selection: $selectedCalender) { -// Text("Gregorian").tag(0) -// Text("Hijri").tag(1) -// } -// .pickerStyle(.segmented) -// .onChange(of: selectedCalender) { value in -// if value == 0 { -// calenderType = .gregorian -// } else { -// calenderType = .hijri(.islamicUmmAlQura) -// } -// } + Picker("", selection: $selectedCalender) { + Text("Gregorian").tag(0) + Text("Hijri").tag(1) + } + .pickerStyle(.segmented) + .onChange(of: selectedCalender) { value in + if value == 0 { + calenderType = .gregorian + } else { + calenderType = .hijri(.islamicUmmAlQura) + } + } switch calenderType { case .gregorian: -// FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates) - - FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates, in: cloaseRange) +// FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates, in: cloaseRange) + + FXDatePickerView(selectedDate: $selectedGregorianDate, specialDates: specialDates, in: Date()...) // .hideMarkers() // .hideDatePicker() -// .disableSwipe() + // .disableSwipe() .calenderType(calenderType) .datePickerTheme(main: .init( diff --git a/FXDatePickerDemo/FXDatePickerDemo/Localizable.xcstrings b/FXDatePickerDemo/FXDatePickerDemo/Localizable.xcstrings index b6a14d0..49f81d6 100644 --- a/FXDatePickerDemo/FXDatePickerDemo/Localizable.xcstrings +++ b/FXDatePickerDemo/FXDatePickerDemo/Localizable.xcstrings @@ -1,8 +1,10 @@ { "sourceLanguage" : "en", "strings" : { + "" : { + + }, "Gregorian" : { - "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -13,7 +15,6 @@ } }, "Hijri" : { - "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { diff --git a/Sources/FXDatePicker/Data/SpecialDate.swift b/Sources/FXDatePicker/Data/SpecialDate.swift index 1c0536e..22e8ba9 100644 --- a/Sources/FXDatePicker/Data/SpecialDate.swift +++ b/Sources/FXDatePicker/Data/SpecialDate.swift @@ -27,7 +27,7 @@ public enum SpecialDateType { public struct ImageType { var imageName: String - public init(imageName: String) { + public init(imageName: String) { self.imageName = imageName } } @@ -36,7 +36,7 @@ public struct SFSymbolsType { var imageName: String var color: Color - public init(imageName: String, color: Color) { + public init(imageName: String, color: Color) { self.imageName = imageName self.color = color } diff --git a/Sources/FXDatePicker/Extensions/Bold+Ext.swift b/Sources/FXDatePicker/Extensions/Bold+Ext.swift index d8c1c87..534918a 100644 --- a/Sources/FXDatePicker/Extensions/Bold+Ext.swift +++ b/Sources/FXDatePicker/Extensions/Bold+Ext.swift @@ -17,7 +17,7 @@ public extension View { self.font(Font.body.bold()) } } else { - self + self } } } diff --git a/Sources/FXDatePicker/Extensions/Calendar+Ext.swift b/Sources/FXDatePicker/Extensions/Calendar+Ext.swift new file mode 100644 index 0000000..061868d --- /dev/null +++ b/Sources/FXDatePicker/Extensions/Calendar+Ext.swift @@ -0,0 +1,18 @@ +//// +//File.swift +// +// +//Created by Basel Baragabah on 11/01/2024. +//Copyright © 2024 Basel Baragabah. All rights reserved. +// + +import Foundation + +extension Calendar { + public func findCurrentDateIndex(dateRange: [Date], selectedDate: Date?) -> Int { + // Find the index of the current date + guard let selectedDate = selectedDate else {return 0} + let currentDate = self.startOfDay(for: selectedDate) + return dateRange.firstIndex(where: { self.isDate($0, equalTo: currentDate, toGranularity: .day) }) ?? (dateRange.count - 1) + } +} diff --git a/Sources/FXDatePicker/Extensions/View+Ext.swift b/Sources/FXDatePicker/Extensions/View+Ext.swift index 160a028..cd89637 100644 --- a/Sources/FXDatePicker/Extensions/View+Ext.swift +++ b/Sources/FXDatePicker/Extensions/View+Ext.swift @@ -12,16 +12,16 @@ extension View { @ViewBuilder func background(_ background: BackgroundStyle) -> some View { switch background { case .color(let color): - self.overlay(Color.clear) + self.overlay(Color.clear) .background(color) case .linearGradient(let gradient, let startPoint, let endPoint): - self.overlay(Color.clear) + self.overlay(Color.clear) .background(LinearGradient(gradient: gradient, startPoint: startPoint, endPoint: endPoint)) case .radialGradient(let gradient, let center, let startRadius, let endRadius): - self.overlay(Color.clear) + self.overlay(Color.clear) .background(RadialGradient(gradient: gradient, center: center, startRadius: startRadius, endRadius: endRadius)) case .angularGradient(let gradient, let center): - self.overlay(Color.clear) + self.overlay(Color.clear) .background(AngularGradient(gradient: gradient, center: center)) } } diff --git a/Sources/FXDatePicker/Healper/findCurrentDateIndex.swift b/Sources/FXDatePicker/Healper/findCurrentDateIndex.swift deleted file mode 100644 index c86cd8e..0000000 --- a/Sources/FXDatePicker/Healper/findCurrentDateIndex.swift +++ /dev/null @@ -1,19 +0,0 @@ -//// -//File.swift -// -// -//Created by Basel Baragabah on 11/01/2024. -//Copyright © 2024 Basel Baragabah. All rights reserved. -// - -import Foundation - -public func findCurrentDateIndex(calendar: Calendar, dateRange: [Date], selectedDate: Date?) -> Int { - // Find the index of the current date - - guard let selectedDate = selectedDate else {return 0} - let currentDate = calendar.startOfDay(for: selectedDate) - return dateRange.firstIndex(where: { calendar.isDate($0, equalTo: currentDate, toGranularity: .day) }) ?? (dateRange.count - 1) - } - - diff --git a/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift b/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift index 6f9b852..401e061 100644 --- a/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift +++ b/Sources/FXDatePicker/Modifiers/ThemeColorModifier.swift @@ -9,9 +9,9 @@ import SwiftUI @available(iOS 14.0, *) - public struct DatePickerThemeKey: EnvironmentKey { +public struct DatePickerThemeKey: EnvironmentKey { public static var defaultValue: DatePickerTheme = DatePickerTheme() - + } public extension EnvironmentValues { diff --git a/Sources/FXDatePicker/UIKit/PickerView.swift b/Sources/FXDatePicker/UIKit/PickerView.swift index 2c3894f..6206aa9 100644 --- a/Sources/FXDatePicker/UIKit/PickerView.swift +++ b/Sources/FXDatePicker/UIKit/PickerView.swift @@ -13,39 +13,39 @@ internal struct FXPickerView: UIViewRepresentable { @Binding var data: [[String]] @Binding var selections: [Int] var textColor: UIColor = .black - + internal func makeCoordinator() -> FXPickerView.Coordinator { Coordinator(self) } - + internal func makeUIView(context: UIViewRepresentableContext) -> UIPickerView { let picker = UIPickerView(frame: .zero) picker.dataSource = context.coordinator picker.delegate = context.coordinator - + return picker } - + internal func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext) { for i in 0...(self.selections.count - 1) { view.selectRow(self.selections[i], inComponent: i, animated: true) } - + DispatchQueue.main.async { view.reloadAllComponents() // Reload components at the end - + // If only one month is available, select it automatically if self.data.count > 1 && self.data[1].count == 1 { self.selections[1] = 0 view.selectRow(0, inComponent: 1, animated: false) } } - + context.coordinator.parent = self } - - + + internal class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate { var parent: FXPickerView @@ -73,7 +73,7 @@ internal struct FXPickerView: UIViewRepresentable { label.textColor = parent.textColor label.textAlignment = .center label.font = UIFont.systemFont(ofSize: 24) - + return label } diff --git a/Sources/FXDatePicker/Views/DayView.swift b/Sources/FXDatePicker/Views/DayView.swift index 0ec1b45..c2d4072 100644 --- a/Sources/FXDatePicker/Views/DayView.swift +++ b/Sources/FXDatePicker/Views/DayView.swift @@ -24,9 +24,9 @@ internal struct DayView: View { @Environment(\.datePickerTheme) private var theme private var isInRange: Bool { - guard let date = date else { return false } - return closeRange.contains(date) - } + guard let date = date else { return false } + return closeRange.contains(date) + } internal var body: some View { VStack(spacing: 0) { @@ -45,11 +45,11 @@ internal struct DayView: View { if !hideMarkers { ZStack { - if let specialDate = specialDate { - specialDateImage(specialDate) - .frame(height: imageSize) - .padding(.vertical, 10) - } + if let specialDate = specialDate { + specialDateImage(specialDate) + .frame(height: imageSize) + .padding(.vertical, 10) + } } @@ -78,16 +78,6 @@ extension DayView { } } -// private func calculateTextColor() -> Color { -// if !isInRange { -// return Color.gray -// } else if isSelected { -// return isToday ? .white : theme.main.accentColor -// } else { -// return isBeforeToday ? theme.main.previousDaysNumber : (isToday ? theme.main.accentColor : theme.main.daysNumbers) -// } -// } - private func calculateTextColor() -> Color { if !isInRange { // Gray out the day if it's not in range @@ -103,7 +93,7 @@ extension DayView { return theme.main.daysNumbers } } - + private var dayFormatter: DateFormatter { let formatter = DateFormatter() diff --git a/Sources/FXDatePicker/Views/FXDatePickerView.swift b/Sources/FXDatePicker/Views/FXDatePickerView.swift index 0ef424a..2a5b559 100644 --- a/Sources/FXDatePicker/Views/FXDatePickerView.swift +++ b/Sources/FXDatePicker/Views/FXDatePickerView.swift @@ -22,7 +22,7 @@ public struct FXDatePickerView: View { private var hideDatePicker: Bool = false @State private var dateRange: [Date] = [] - + @State private var openShowSelectedMonths: Bool = false @State private var arrowRotation: Double = 0 @@ -64,12 +64,12 @@ public struct FXDatePickerView: View { guard let previousMonth = calendar.date(byAdding: .month, value: -1, to: displayedMonth) else { return false } return previousMonth >= closeRange.lowerBound } - + private var canMoveToNextMonth: Bool { guard let nextMonth = calendar.date(byAdding: .month, value: 1, to: displayedMonth) else { return false } return nextMonth <= closeRange.upperBound } - + // MARK:- with specialDates public init(selectedDate: Binding, specialDates: [SpecialDate]) { self._selectedDate = selectedDate @@ -77,9 +77,9 @@ public struct FXDatePickerView: View { let start = calendar.date(byAdding: .year, value: -50, to: Date()) ?? Date() let end = calendar.date(byAdding: .year, value: 50, to: Date()) ?? Date() - + self.closeRange = start...end - + self._displayedMonth = State(initialValue: Date()) } @@ -98,7 +98,7 @@ public struct FXDatePickerView: View { let startOfToday = calendar.startOfDay(for: Date()) let end = calendar.date(byAdding: .year, value: 50, to: Date()) ?? Date() - + self.closeRange = startOfToday...end self._displayedMonth = State(initialValue: startOfToday) @@ -123,7 +123,7 @@ public struct FXDatePickerView: View { let start = calendar.date(byAdding: .year, value: -50, to: Date()) ?? Date() let end = calendar.date(byAdding: .year, value: 50, to: Date()) ?? Date() - + self.closeRange = start...end self._displayedMonth = State(initialValue: Date()) @@ -145,9 +145,9 @@ public struct FXDatePickerView: View { let startOfToday = calendar.startOfDay(for: Date()) let end = calendar.date(byAdding: .year, value: 50, to: Date()) ?? Date() - + self.closeRange = startOfToday...end - + self._displayedMonth = State(initialValue: closeRange.lowerBound) self.hideMarkers = true @@ -158,7 +158,7 @@ public struct FXDatePickerView: View { self.specialDates = [] let start = calendar.date(byAdding: .year, value: -50, to: Date()) ?? Date() - + self.closeRange = start...(range.upperBound + 1) self._displayedMonth = State(initialValue: closeRange.upperBound) @@ -222,17 +222,17 @@ public struct FXDatePickerView: View { hideMarkers: hideMarkers, closeRange: closeRange) } else { - // ForEach($dateRange, id: \.self) { $month in - ForEach(0.. Int { - // Find the index of the current date - return dateRange.firstIndex(where: { calendar.isDate($0, equalTo: currentDate, toGranularity: .day) }) ?? (dateRange.count - 1) - } } //MARK:- Modifiers @@ -301,7 +296,7 @@ public extension FXDatePickerView { private func setupCurrentDate() { dateRange = generateMonths(start: closeRange.lowerBound, end: closeRange.upperBound) } - + private func generateMonths(start: Date, end: Date) -> [Date] { var months: [Date] = [] var currentDate = start @@ -313,7 +308,7 @@ public extension FXDatePickerView { return months } - + func changeMonth(by increment: Int) { guard let newMonth = calendar.date(byAdding: .month, value: increment, to: displayedMonth), let newMonthStart = calendar.date(from: calendar.dateComponents([.year, .month], from: newMonth)) else { @@ -327,7 +322,7 @@ public extension FXDatePickerView { print("New month is outside the close range") } } - + private func getMonthName(from date: Date) -> String { let formatter = monthFormatter return formatter.string(from: date) diff --git a/Sources/FXDatePicker/Views/MonthView.swift b/Sources/FXDatePicker/Views/MonthView.swift index 0f76bb3..4bea014 100644 --- a/Sources/FXDatePicker/Views/MonthView.swift +++ b/Sources/FXDatePicker/Views/MonthView.swift @@ -21,13 +21,6 @@ internal struct MonthView: View { let hideMarkers: Bool let closeRange: ClosedRange - private var totalCells: Int { 6 * 7 } // 6 rows, 7 columns - - - private var firstDayOfMonth: Date { - calendar.date(from: calendar.dateComponents([.year, .month], from: displayedMonth)) ?? Date() - } - private var firstDayOfWeekInMonth: Int { guard let firstDayOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: displayedMonth)) else { return 0 @@ -53,21 +46,21 @@ internal struct MonthView: View { private let totalMonthViewHeight: CGFloat = 300 private let maxRows: Int = 6 - + internal var body: some View { let rowHeight: CGFloat = (hideMarkers == false) ? 50 : 45 let totalRowHeight = CGFloat(numberOfRows) * rowHeight let totalPaddingHeight = totalMonthViewHeight - totalRowHeight let paddingPerRow = totalPaddingHeight / CGFloat(maxRows - 2) - - LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 7), spacing: paddingPerRow) { - ForEach(0..<(numberOfRows * 7), id: \.self) { index in - gridCellView(at: index) - } + + LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 7), spacing: paddingPerRow) { + ForEach(0..<(numberOfRows * 7), id: \.self) { index in + gridCellView(at: index) } - .frame(height: totalMonthViewHeight) - } + } + .frame(height: totalMonthViewHeight) + } } extension MonthView { @@ -92,8 +85,8 @@ extension MonthView { Text("").frame(height: 50) } } - - + + private func getDateFor(day: Int) -> Date { calendar.date(from: DateComponents(year: calendar.component(.year, from: displayedMonth), month: calendar.component(.month, from: displayedMonth), day: day))! } diff --git a/Sources/FXDatePicker/Views/SelectMonthPickerView.swift b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift index 593ceaa..1d210e9 100644 --- a/Sources/FXDatePicker/Views/SelectMonthPickerView.swift +++ b/Sources/FXDatePicker/Views/SelectMonthPickerView.swift @@ -14,35 +14,35 @@ internal struct SelectMonthPickerView: View { let calenderType: CalenderType let closeRange: ClosedRange @Binding var selectedDate: Date - + @State private var pickerData: [[String]] = [[]] @State private var selections: [Int] = [] - + private var years: [Int] { let startYear = calendar.component(.year, from: closeRange.lowerBound) let endYear = calendar.component(.year, from: closeRange.upperBound) return Array(startYear...endYear) } - + init(selectedDate: Binding, calendar: Calendar, calenderType: CalenderType, closeRange: ClosedRange) { self.calendar = calendar self._selectedDate = selectedDate self.calenderType = calenderType self.closeRange = closeRange - + let currentYear = calendar.component(.year, from: selectedDate.wrappedValue) let currentMonth = calendar.component(.month, from: selectedDate.wrappedValue) - 1 let yearIndex = years.firstIndex(of: currentYear) ?? 0 - + let initialMonths = getMonths(for: currentYear) self._pickerData = State(initialValue: [years.map { String($0) }, initialMonths]) self._selections = State(initialValue: [yearIndex, currentMonth]) } - + var body: some View { ZStack { FXBackgroundView(background: theme.main.backgroundStyle) - + FXPickerView(data: $pickerData, selections: $selections, textColor: UIColor(theme.main.monthTitle)) .onChange(of: selections[0]) { yearIndex in let selectedYear = years[yearIndex] @@ -53,11 +53,11 @@ internal struct SelectMonthPickerView: View { } } } - + private func getMonths(for year: Int) -> [String] { let isStartYear = year == calendar.component(.year, from: closeRange.lowerBound) let isEndYear = year == calendar.component(.year, from: closeRange.upperBound) - + if isStartYear { let firstMonth = calendar.component(.month, from: closeRange.lowerBound) return Array(calendar.monthSymbols.dropFirst(firstMonth - 1)) @@ -68,13 +68,13 @@ internal struct SelectMonthPickerView: View { return calendar.monthSymbols } } - + func updateSelectedDate(with selections: [Int]) { var components = DateComponents() components.year = years[selections[0]] components.month = selections[1] + 1 components.day = calendar.component(.day, from: selectedDate) - + if let updatedDate = calendar.date(from: components), closeRange.contains(updatedDate) { selectedDate = updatedDate } diff --git a/Sources/FXDatePicker/Views/SwipeView.swift b/Sources/FXDatePicker/Views/SwipeView.swift index 9185101..57d5750 100644 --- a/Sources/FXDatePicker/Views/SwipeView.swift +++ b/Sources/FXDatePicker/Views/SwipeView.swift @@ -24,21 +24,20 @@ internal struct SwipeView: View { self.content = content self.calendar = calendar self._selectedIndex = selectedIndex - + } - + var body: some View { Group { if isDisable == false { TabView(selection: $selectedIndex) { - content() - + content() } .tabViewStyle(.page(indexDisplayMode: .never)) .onAppear { let selectedDate = calendar.startOfDay(for: Date()) print("selectedDate: \(selectedDate)") - selectedIndex = findCurrentDateIndex(calendar: calendar, dateRange: dateRange, selectedDate: selectedDate) + selectedIndex = calendar.findCurrentDateIndex(dateRange: dateRange, selectedDate: selectedDate) } .onChange(of: selectedIndex) { newValue in displayedMonth = dateRange[newValue] @@ -48,6 +47,6 @@ internal struct SwipeView: View { } } } - + }