diff --git a/README.md b/README.md index b695182..4c2c32e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Fastis is a fully customisable UI component for picking dates and ranges created - [Usage](#usage) - [Quick Start](#quick-start) - [Single and range modes](#single-and-range-modes) + - [Marked dates](#marked-dates) - [Configuration](#configuration) - [Shortcuts](#shortcuts) - [Customization](#customization) @@ -150,6 +151,57 @@ fastisController.dismissHandler = { [weak self] action in } ``` +### Marked dates + +If you want to show a red dot under specific days, you can pass an array of dates to `markedDates`. +These dates are used only for display. They do not change the picker result type, so Fastis still works in single-date or range mode. + +You can customize the marker globally through `FastisConfig.dayCell`: + +```swift +FastisConfig.default.dayCell.markerColor = .systemGreen +FastisConfig.default.dayCell.markerSize = 6 +``` + +UIKit: + +```swift +let markedDates = [ + Date(), + Calendar.current.date(byAdding: .day, value: 2, to: Date())! +] + +let fastisController = FastisController(mode: .single) +fastisController.markedDates = markedDates +fastisController.dismissHandler = { action in + switch action { + case .done(let resultDate): + print(resultDate) + case .cancel: + break + } +} +``` + +SwiftUI: + +```swift +let markedDates = [ + Date(), + Calendar.current.date(byAdding: .day, value: 2, to: Date())! +] + +FastisView(mode: .single) { action in + switch action { + case .done(let resultDate): + print(resultDate) + case .cancel: + break + } +} +.markedDates(markedDates) +``` + ### Configuration FastisController has the following default configuration parameters: @@ -159,6 +211,7 @@ var shortcuts: [FastisShortcut] = [] var allowsToChooseNilDate: Bool = false var dismissHandler: ((DismissAction) -> Void)? = nil var initialValue: Value? = nil +var markedDates: [Date] = [] var minimumDate: Date? = nil var maximumDate: Date? = nil var selectMonthOnHeaderTap: Bool = true @@ -170,6 +223,7 @@ var closeOnSelectionImmediately: Bool = false - `allowsToChooseNilDate`- Allow to choose `nil` date. If you set `true`, the done button will always be enabled and you will be able to reset selection by you tapping on selected date again. The default value is `false`. - `dismissHandler`- The block to execute after the dismissal finishes. The default value is `nil`. Return DismissAction.done(FastisValue?) after the "Done" button will be tapped or DismissAction.cancel when controller dismissed without tapped the "Done" button. - `initialValue`- And initial value which will be selected by default. The default value is `nil`. +- `markedDates`- Dates that should display a red marker dot in the calendar. The default value is `[]`. - `minimumDate`- Minimal selection date. Dates less than current will be marked as unavailable. The default value is `nil`. - `maximumDate`- Maximum selection date. Dates more significant than current will be marked as unavailable. The default value is `nil`. - `selectMonthOnHeaderTap` (Only for `.range` mode) - Set this variable to `true` if you want to allow select date ranges by tapping on months. The default value is `true`. diff --git a/Sources/Views/DayCell.swift b/Sources/Views/DayCell.swift index d53bb33..f418ae1 100644 --- a/Sources/Views/DayCell.swift +++ b/Sources/Views/DayCell.swift @@ -27,6 +27,14 @@ final class DayCell: JTACDayCell { return view }() + lazy var markerView: UIView = { + let view = UIView() + view.backgroundColor = .systemRed + view.isHidden = true + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + lazy var selectionBackgroundView: UIView = { let view = UIView() view.isHidden = true @@ -52,6 +60,8 @@ final class DayCell: JTACDayCell { private var rangeViewLeftAnchorToCenterConstraint: NSLayoutConstraint? private var rangeViewRightAnchorToSuperviewConstraint: NSLayoutConstraint? private var rangeViewRightAnchorToCenterConstraint: NSLayoutConstraint? + private var markerViewWidthConstraint: NSLayoutConstraint? + private var markerViewHeightConstraint: NSLayoutConstraint? // MARK: - Lifecycle @@ -88,6 +98,9 @@ final class DayCell: JTACDayCell { self.selectionBackgroundView.backgroundColor = config.selectedBackgroundColor self.dateLabel.font = config.dateLabelFont self.dateLabel.textColor = config.dateLabelColor + self.markerView.backgroundColor = config.markerColor + self.markerViewWidthConstraint?.constant = config.markerSize + self.markerViewHeightConstraint?.constant = config.markerSize if let cornerRadius = config.customSelectionViewCornerRadius { self.selectionBackgroundView.layer.cornerRadius = cornerRadius } @@ -99,6 +112,7 @@ final class DayCell: JTACDayCell { self.contentView.addSubview(self.backgroundRangeView) self.contentView.addSubview(self.selectionBackgroundView) self.contentView.addSubview(self.dateLabel) + self.contentView.addSubview(self.markerView) self.selectionBackgroundView.layer.cornerRadius = min(self.frame.width, self.frame.height) / 2 } @@ -109,6 +123,16 @@ final class DayCell: JTACDayCell { self.dateLabel.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor) ]) + self.markerViewWidthConstraint = self.markerView.widthAnchor.constraint(equalToConstant: self.config.markerSize) + self.markerViewHeightConstraint = self.markerView.heightAnchor.constraint(equalToConstant: self.config.markerSize) + + NSLayoutConstraint.activate([ + self.markerView.centerXAnchor.constraint(equalTo: self.dateLabel.centerXAnchor), + self.markerView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -6), + self.markerViewWidthConstraint, + self.markerViewHeightConstraint + ].compactMap { $0 }) + self.rangeViewLeftAnchorToSuperviewConstraint = self.backgroundRangeView.leftAnchor.constraint(equalTo: self.contentView.leftAnchor) self.rangeViewLeftAnchorToCenterConstraint = self.backgroundRangeView.leftAnchor.constraint(equalTo: self.contentView.centerXAnchor) @@ -278,6 +302,7 @@ final class DayCell: JTACDayCell { var isDateEnabled = true var rangeView = RangeViewConfig() var isToday = false + var showsMarker = false } internal func configure(for config: ViewConfig) { @@ -285,6 +310,8 @@ final class DayCell: JTACDayCell { self.selectionBackgroundView.isHidden = config.isSelectedViewHidden self.isUserInteractionEnabled = config.dateLabelText != nil && config.isDateEnabled self.clipsToBounds = config.dateLabelText == nil + self.markerView.layer.cornerRadius = 2.5 + self.markerView.isHidden = !config.showsMarker || config.dateLabelText == nil if let dateLabelText = config.dateLabelText { self.dateLabel.isHidden = false @@ -367,6 +394,9 @@ final class DayCell: JTACDayCell { private func configureTodayCell(viewConfig: ViewConfig, todayConfig: FastisConfig.TodayCell) { self.dateLabel.font = todayConfig.dateLabelFont + let usesMarkerStyle = viewConfig.showsMarker + let circleSize = usesMarkerStyle ? self.config.markerSize : todayConfig.circleSize + if !viewConfig.isDateEnabled { self.dateLabel.textColor = todayConfig.dateLabelUnavailableColor self.circleView.backgroundColor = todayConfig.circleViewUnavailableColor @@ -375,20 +405,21 @@ final class DayCell: JTACDayCell { self.circleView.backgroundColor = todayConfig.circleViewSelectedColor } else if !viewConfig.rangeView.isHidden { self.dateLabel.textColor = todayConfig.onRangeLabelColor - self.circleView.backgroundColor = todayConfig.onRangeLabelColor + self.circleView.backgroundColor = usesMarkerStyle ? self.config.markerColor : todayConfig.circleViewOnRangeColor } else { self.dateLabel.textColor = todayConfig.dateLabelColor - self.circleView.backgroundColor = todayConfig.circleViewColor + self.circleView.backgroundColor = usesMarkerStyle ? self.config.markerColor : todayConfig.circleViewColor } - self.circleView.layer.cornerRadius = todayConfig.circleSize * 0.5 + self.markerView.isHidden = true + self.circleView.layer.cornerRadius = circleSize * 0.5 self.circleView.removeFromSuperview() self.contentView.addSubview(self.circleView) NSLayoutConstraint.activate([ self.circleView.centerXAnchor.constraint(equalTo: self.dateLabel.centerXAnchor), self.circleView.topAnchor.constraint(equalTo: self.dateLabel.bottomAnchor, constant: todayConfig.circleVerticalInset), - self.circleView.widthAnchor.constraint(equalToConstant: todayConfig.circleSize), - self.circleView.heightAnchor.constraint(equalToConstant: todayConfig.circleSize) + self.circleView.widthAnchor.constraint(equalToConstant: circleSize), + self.circleView.heightAnchor.constraint(equalToConstant: circleSize) ]) } @@ -431,6 +462,20 @@ public extension FastisConfig { */ public var selectedBackgroundColor: UIColor = .systemBlue + /** + Color of marker dot displayed under marked dates + + Default value — `.systemRed` + */ + public var markerColor: UIColor = .systemRed + + /** + Size of marker dot displayed under marked dates + + Default value — `5pt` + */ + public var markerSize: CGFloat = 5 + /** Color of date label in cell when date is selected diff --git a/Sources/Views/FastisController.swift b/Sources/Views/FastisController.swift index 24dc2a5..6b6bbae 100644 --- a/Sources/Views/FastisController.swift +++ b/Sources/Views/FastisController.swift @@ -157,6 +157,7 @@ open class FastisController: UIViewController, JTACMonthView private var dayFormatter = DateFormatter() private var isDone = false private var privateCloseOnSelectionImmediately = false + private var markedDateSet = Set() private var value: Value? { didSet { @@ -212,6 +213,23 @@ open class FastisController: UIViewController, JTACMonthView */ public var initialValue: Value? + /** + Dates that should display a red marker dot in the calendar. + + Default value - `"[]"` + */ + public var markedDates: [Date] = [] { + didSet { + self.markedDateSet = Set( + self.markedDates.map { $0.startOfDay(in: self.config.calendar) } + ) + self.viewConfigs.removeAll() + if self.isViewLoaded { + self.calendarView.reloadData() + } + } + } + /** Minimal selection date. Dates less then current will be marked as unavailable @@ -400,6 +418,9 @@ open class FastisController: UIViewController, JTACMonthView if newConfig.dateLabelText != nil { newConfig.dateLabelText = self.dayFormatter.string(from: date) + newConfig.showsMarker = self.markedDateSet.contains( + date.startOfDay(in: self.config.calendar) + ) } if self.config.calendar.isDateInToday(date) { diff --git a/Sources/Views/FastisView.swift b/Sources/Views/FastisView.swift index ea651de..1e9062b 100644 --- a/Sources/Views/FastisView.swift +++ b/Sources/Views/FastisView.swift @@ -144,6 +144,16 @@ public struct FastisView: UIViewControllerRepresentable { return self } + /** + Dates that should display a red marker dot in the calendar. + + Default value - `"[]"` + */ + public func markedDates(_ value: [Date]) -> Self { + self.controller.markedDates = value + return self + } + } public extension FastisView where Value == FastisRange {