From 0e37d90135831c42e27fb0e383675a74cbc5ac51 Mon Sep 17 00:00:00 2001 From: UriyDevyataev <91690559+UriyDevyataev@users.noreply.github.com> Date: Tue, 14 Nov 2023 19:13:20 +0300 Subject: [PATCH] Support custom calendar (Issue #40) * support custom calendar - add button with custom calendar in example - readme * unavailable locale in CurrentValueView and MonthHeader --- Example/Source/ViewController.swift | 49 +++++++++++++++++++++-- README.md | 13 ++++++ Sources/Models/Shortcut.swift | 32 +++++++-------- Sources/Views/Controller.swift | 28 +++++++------ Sources/Views/CurrentValueView.swift | 12 ++++-- Sources/Views/DayCell.swift | 1 - Sources/Views/MonthHeader.swift | 16 +++++--- Sources/Views/ShortcutContainerView.swift | 6 +-- 8 files changed, 112 insertions(+), 45 deletions(-) diff --git a/Example/Source/ViewController.swift b/Example/Source/ViewController.swift index 8b73693..aad16ae 100644 --- a/Example/Source/ViewController.swift +++ b/Example/Source/ViewController.swift @@ -40,16 +40,27 @@ class ViewController: UIViewController { return button }() + private lazy var chooseSingleButtonWithCustomCalendar: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Choose single date with custom calendar", for: .normal) + button.addTarget(self, action: #selector(self.chooseSingleDateWithCustomCalendar), for: .touchUpInside) + return button + }() + // MARK: - Variables + private lazy var dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "dd/MM/yyyy" + return formatter + }() + private var currentValue: FastisValue? { didSet { - let formatter = DateFormatter() - formatter.dateFormat = "dd/MM/yyyy" if let rangeValue = self.currentValue as? FastisRange { - self.currentDateLabel.text = formatter.string(from: rangeValue.fromDate) + " - " + formatter.string(from: rangeValue.toDate) + self.currentDateLabel.text = self.dateFormatter.string(from: rangeValue.fromDate) + " - " + self.dateFormatter.string(from: rangeValue.toDate) } else if let date = self.currentValue as? Date { - self.currentDateLabel.text = formatter.string(from: date) + self.currentDateLabel.text = self.dateFormatter.string(from: date) } else { self.currentDateLabel.text = "Choose a date" } @@ -79,6 +90,7 @@ class ViewController: UIViewController { self.containerView.setCustomSpacing(32, after: self.currentDateLabel) self.containerView.addArrangedSubview(self.chooseRangeButton) self.containerView.addArrangedSubview(self.chooseSingleButton) + self.containerView.addArrangedSubview(self.chooseSingleButtonWithCustomCalendar) self.view.addSubview(self.containerView) } @@ -97,6 +109,7 @@ class ViewController: UIViewController { @objc private func chooseRange() { + self.dateFormatter.calendar = .current let fastisController = FastisController(mode: .range) fastisController.title = "Choose range" fastisController.initialValue = self.currentValue as? FastisRange @@ -117,6 +130,7 @@ class ViewController: UIViewController { @objc private func chooseSingleDate() { + self.dateFormatter.calendar = .current let fastisController = FastisController(mode: .single) fastisController.title = "Choose date" fastisController.initialValue = self.currentValue as? Date @@ -133,4 +147,31 @@ class ViewController: UIViewController { fastisController.present(above: self) } + @objc + private func chooseSingleDateWithCustomCalendar() { + var customConfig: FastisConfig = .default + var calendar: Calendar = .init(identifier: .islamicUmmAlQura) + calendar.locale = .autoupdatingCurrent + customConfig.calendar = calendar + + self.dateFormatter.calendar = calendar + + let fastisController = FastisController(mode: .range, config: customConfig) + fastisController.title = "Choose range" + fastisController.initialValue = self.currentValue as? FastisRange + fastisController.minimumDate = calendar.date(byAdding: .month, value: -2, to: Date()) + fastisController.maximumDate = calendar.date(byAdding: .month, value: 3, to: Date()) + fastisController.allowToChooseNilDate = true + fastisController.shortcuts = [.today, .lastWeek, .lastMonth] + fastisController.dismissHandler = { [weak self] action in + switch action { + case .done(let newValue): + self?.currentValue = newValue + case .cancel: + print("any actions") + } + } + fastisController.present(above: self) + } + } diff --git a/README.md b/README.md index 7bea492..4ac156c 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,19 @@ var customConfig = FastisConfig.default customConfig.controller.dayCell.dateLabelColor = .blue let fastisController = FastisController(mode: .range, config: customConfig) ``` + +To customise a special FastisController instance with custom calendar: + +```swift +var customConfig: FastisConfig = .default +var calendar: Calendar = .init(identifier: .islamicUmmAlQura) +calendar.locale = .autoupdatingCurrent +customConfig.calendar = calendar +let fastisController = FastisController(mode: .range, config: customConfig) +fastisController.minimumDate = calendar.date(byAdding: .month, value: -2, to: Date()) +fastisController.maximumDate = calendar.date(byAdding: .month, value: 3, to: Date()) +``` + To customise a today cell: ```swift diff --git a/Sources/Models/Shortcut.swift b/Sources/Models/Shortcut.swift index 50c37a4..a2b573a 100644 --- a/Sources/Models/Shortcut.swift +++ b/Sources/Models/Shortcut.swift @@ -20,7 +20,7 @@ import Foundation Also you can create your own shortcut: ```swift - var customShortcut = FastisShortcut(name: "Today") { + var customShortcut = FastisShortcut(name: "Today") { calendar in let now = Date() return FastisRange(from: now.startOfDay(), to: now.endOfDay()) } @@ -35,13 +35,13 @@ public struct FastisShortcut: Hashable { public var name: String /// Tap handler - public var action: () -> Value + public var action: (Calendar) -> Value /// Create a shortcut /// - Parameters: /// - name: Display name of shortcut /// - action: Tap handler - public init(name: String, action: @escaping () -> Value) { + public init(name: String, action: @escaping (Calendar) -> Value) { self.name = name self.action = action } @@ -54,10 +54,10 @@ public struct FastisShortcut: Hashable { lhs.id == rhs.id } - internal func isEqual(to value: Value) -> Bool { - if let date1 = self.action() as? Date, let date2 = value as? Date { + internal func isEqual(to value: Value, calendar: Calendar) -> Bool { + if let date1 = self.action(calendar) as? Date, let date2 = value as? Date { return date1.isInSameDay(date: date2) - } else if let value1 = self.action() as? FastisRange, let value2 = value as? FastisRange { + } else if let value1 = self.action(calendar) as? FastisRange, let value2 = value as? FastisRange { return value1 == value2 } return false @@ -69,7 +69,7 @@ public extension FastisShortcut where Value == FastisRange { /// Range: from **`now.startOfDay`** to **`now.endOfDay`** static var today: FastisShortcut { - FastisShortcut(name: "Today") { + FastisShortcut(name: "Today") { _ in let now = Date() return FastisRange(from: now.startOfDay(), to: now.endOfDay()) } @@ -77,18 +77,18 @@ public extension FastisShortcut where Value == FastisRange { /// Range: from **`now.startOfDay - 7 days`** to **`now.endOfDay`** static var lastWeek: FastisShortcut { - FastisShortcut(name: "Last week") { + FastisShortcut(name: "Last week") { calendar in let now = Date() - let weekAgo = Calendar.current.date(byAdding: .day, value: -7, to: now)! + let weekAgo = calendar.date(byAdding: .day, value: -7, to: now)! return FastisRange(from: weekAgo.startOfDay(), to: now.endOfDay()) } } /// Range: from **`now.startOfDay - 1 month`** to **`now.endOfDay`** static var lastMonth: FastisShortcut { - FastisShortcut(name: "Last month") { + FastisShortcut(name: "Last month") { calendar in let now = Date() - let monthAgo = Calendar.current.date(byAdding: .month, value: -1, to: now)! + let monthAgo = calendar.date(byAdding: .month, value: -1, to: now)! return FastisRange(from: monthAgo.startOfDay(), to: now.endOfDay()) } } @@ -99,22 +99,22 @@ public extension FastisShortcut where Value == Date { /// Date value: **`now`** static var today: FastisShortcut { - FastisShortcut(name: "Today") { + FastisShortcut(name: "Today") { _ in Date() } } /// Date value: **`now - .day(1)`** static var yesterday: FastisShortcut { - FastisShortcut(name: "Yesterday") { - Calendar.current.date(byAdding: .day, value: -1, to: Date())! + FastisShortcut(name: "Yesterday") { calendar in + calendar.date(byAdding: .day, value: -1, to: Date())! } } /// Date value: **`now + .day(1)`** static var tomorrow: FastisShortcut { - FastisShortcut(name: "Tomorrow") { - Calendar.current.date(byAdding: .day, value: 1, to: Date())! + FastisShortcut(name: "Tomorrow") { calendar in + calendar.date(byAdding: .day, value: 1, to: Date())! } } diff --git a/Sources/Views/Controller.swift b/Sources/Views/Controller.swift index 171aaed..e48e774 100644 --- a/Sources/Views/Controller.swift +++ b/Sources/Views/Controller.swift @@ -126,7 +126,10 @@ open class FastisController: UIViewController, JTACMonthView }() private lazy var currentValueView: CurrentValueView = { - let view = CurrentValueView(config: self.config.currentValueView) + let view = CurrentValueView( + config: self.config.currentValueView, + calendar: self.config.calendar + ) view.currentValue = self.value view.translatesAutoresizingMaskIntoConstraints = false view.onClear = { [weak self] in @@ -143,11 +146,13 @@ open class FastisController: UIViewController, JTACMonthView ) view.translatesAutoresizingMaskIntoConstraints = false if let value = self.value { - view.selectedShortcut = self.shortcuts.first(where: { $0.isEqual(to: value) }) + view.selectedShortcut = self.shortcuts.first(where: { + $0.isEqual(to: value, calendar: self.config.calendar) + }) } view.onSelect = { [weak self] selectedShortcut in guard let self else { return } - let newValue = selectedShortcut.action() + let newValue = selectedShortcut.action(self.config.calendar) if !newValue.outOfRange(minDate: self.privateMinimumDate, maxDate: self.privateMaximumDate) { self.value = newValue self.selectValue(newValue, in: self.calendarView) @@ -266,6 +271,7 @@ open class FastisController: UIViewController, JTACMonthView self.config = config self.appearance = config.controller self.dayFormatter.locale = config.calendar.locale + self.dayFormatter.calendar = config.calendar self.dayFormatter.dateFormat = "d" super.init(nibName: nil, bundle: nil) } @@ -418,7 +424,7 @@ open class FastisController: UIViewController, JTACMonthView newConfig.dateLabelText = self.dayFormatter.string(from: date) } - if Calendar.current.isDateInToday(date) { + if self.config.calendar.isDateInToday(date) { newConfig.isToday = true } @@ -433,7 +439,9 @@ open class FastisController: UIViewController, JTACMonthView private func updateSelectedShortcut() { guard !self.shortcuts.isEmpty else { return } if let value = self.value { - self.shortcutContainerView.selectedShortcut = self.shortcuts.first(where: { $0.isEqual(to: value) }) + self.shortcutContainerView.selectedShortcut = self.shortcuts.first(where: { + $0.isEqual(to: value, calendar: self.config.calendar) + }) } else { self.shortcutContainerView.selectedShortcut = nil } @@ -545,12 +553,8 @@ open class FastisController: UIViewController, JTACMonthView public func configureCalendar(_ calendar: JTACMonthView) -> ConfigurationParameters { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy MM dd" - dateFormatter.timeZone = self.config.calendar.timeZone - dateFormatter.locale = self.config.calendar.locale - var startDate = dateFormatter.date(from: "2000 01 01")! - var endDate = dateFormatter.date(from: "2030 12 01")! + var startDate = self.config.calendar.date(byAdding: .year, value: -99, to: Date())! + var endDate = self.config.calendar.date(byAdding: .year, value: 99, to: Date())! if let maximumDate = self.privateMaximumDate, let endOfNextMonth = self.config.calendar.date(byAdding: .month, value: 2, to: maximumDate)? @@ -588,7 +592,7 @@ open class FastisController: UIViewController, JTACMonthView withReuseIdentifier: self.monthHeaderReuseIdentifier, for: indexPath ) as! MonthHeader - header.applyConfig(self.config.monthHeader) + header.applyConfig(self.config.monthHeader, calendar: self.config.calendar) header.configure(for: range.start) if self.privateSelectMonthOnHeaderTap, Value.mode == .range { header.tapHandler = { [weak self, weak calendar] in diff --git a/Sources/Views/CurrentValueView.swift b/Sources/Views/CurrentValueView.swift index fbf9577..f731717 100644 --- a/Sources/Views/CurrentValueView.swift +++ b/Sources/Views/CurrentValueView.swift @@ -42,14 +42,16 @@ final class CurrentValueView: UIView { // MARK: - Variables private let config: FastisConfig.CurrentValueView + private let calendar: Calendar /// Clear button tap handler internal var onClear: (() -> Void)? private lazy var dateFormatter: DateFormatter = { let formatter = DateFormatter() - formatter.locale = self.config.locale + formatter.locale = self.calendar.locale formatter.dateFormat = self.config.format + formatter.calendar = self.calendar return formatter }() @@ -61,8 +63,9 @@ final class CurrentValueView: UIView { // MARK: - Lifecycle - internal init(config: FastisConfig.CurrentValueView) { + internal init(config: FastisConfig.CurrentValueView, calendar: Calendar) { self.config = config + self.calendar = calendar super.init(frame: .zero) self.configureUI() self.configureSubviews() @@ -235,6 +238,9 @@ public extension FastisConfig { Default value — `Locale.autoupdatingCurrent` */ - public var locale: Locale = .autoupdatingCurrent + @available(*, unavailable, message: "Use locale in FastisConfig.calendar.locale") + public var locale: Locale { + .autoupdatingCurrent + } } } diff --git a/Sources/Views/DayCell.swift b/Sources/Views/DayCell.swift index 2fc5fe9..06eecef 100644 --- a/Sources/Views/DayCell.swift +++ b/Sources/Views/DayCell.swift @@ -166,7 +166,6 @@ final class DayCell: JTACDayCell { if let value = rangeValue { - let calendar = Calendar.current var showRangeView = false if state.dateBelongsTo == .followingMonthWithinBoundary { diff --git a/Sources/Views/MonthHeader.swift b/Sources/Views/MonthHeader.swift index d10f4b3..f4b36ff 100644 --- a/Sources/Views/MonthHeader.swift +++ b/Sources/Views/MonthHeader.swift @@ -37,7 +37,7 @@ final class MonthHeader: JTACMonthReusableView { super.init(frame: frame) self.configureSubviews() self.configureConstraints() - self.applyConfig(FastisConfig.default.monthHeader) + self.applyConfig(FastisConfig.default.monthHeader, calendar: .current) let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.viewTapped)) self.addGestureRecognizer(tapRecognizer) } @@ -69,9 +69,10 @@ final class MonthHeader: JTACMonthReusableView { // MARK: - Actions - internal func applyConfig(_ config: FastisConfig.MonthHeader) { + internal func applyConfig(_ config: FastisConfig.MonthHeader, calendar: Calendar) { + self.monthFormatter.calendar = calendar self.monthFormatter.dateFormat = config.monthFormat - self.monthFormatter.locale = config.monthLocale + self.monthFormatter.locale = calendar.locale self.monthLabel.font = config.labelFont self.monthLabel.textColor = config.labelColor self.monthLabel.textAlignment = config.labelAlignment @@ -128,16 +129,19 @@ public extension FastisConfig { /** Format of displayed month value - Default value — `"LLLL yyyy"` + Default value — `"MMMM yyyy"` */ - public var monthFormat = "LLLL yyyy" + public var monthFormat = "MMMM yyyy" /** Locale of displayed month value Default value — `.current` */ - public var monthLocale: Locale = .current + @available(*, unavailable, message: "Use locale FastisConfig.calendar.locale") + public var monthLocale: Locale { + .autoupdatingCurrent + } /** Height of month view diff --git a/Sources/Views/ShortcutContainerView.swift b/Sources/Views/ShortcutContainerView.swift index 4b6d71c..90f3dd8 100644 --- a/Sources/Views/ShortcutContainerView.swift +++ b/Sources/Views/ShortcutContainerView.swift @@ -66,11 +66,11 @@ final class ShortcutContainerView: UIView { // MARK: - Configuration - func configureUI() { + private func configureUI() { self.backgroundColor = self.config.backgroundColor } - func configureSubviews() { + private func configureSubviews() { self.scrollView.addSubview(self.stackView) self.addSubview(self.scrollView) for (i, item) in self.shortcuts.enumerated() { @@ -85,7 +85,7 @@ final class ShortcutContainerView: UIView { } } - func configureConstraints() { + private func configureConstraints() { NSLayoutConstraint.activate([ self.stackView.leftAnchor.constraint(equalTo: self.scrollView.contentLayoutGuide.leftAnchor, constant: self.config.insets.left), self.stackView.rightAnchor.constraint(