diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 10329d45..b1a2ac68 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -159,6 +159,8 @@ EAC58C182BED0E2300BA39FA /* SecurityCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C172BED0E2300BA39FA /* SecurityCode.swift */; }; EAC58C232BF2824200BA39FA /* DatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C222BF2824200BA39FA /* DatePicker.swift */; }; EAC58C252BF2A7FB00BA39FA /* DatePickerChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */; }; + EAC58C272BF4116200BA39FA /* DatePickerCalendarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */; }; + EAC58C292BF4118C00BA39FA /* DatePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */; }; EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */; }; EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */; }; EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */; }; @@ -373,6 +375,8 @@ EAC58C172BED0E2300BA39FA /* SecurityCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityCode.swift; sourceTree = ""; }; EAC58C222BF2824200BA39FA /* DatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePicker.swift; sourceTree = ""; }; EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = DatePickerChangeLog.txt; sourceTree = ""; }; + EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerCalendarModel.swift; sourceTree = ""; }; + EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerViewController.swift; sourceTree = ""; }; EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupCollectionViewCell.swift; sourceTree = ""; }; @@ -947,6 +951,8 @@ isa = PBXGroup; children = ( EAC58C222BF2824200BA39FA /* DatePicker.swift */, + EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */, + EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */, EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */, ); path = DatePicker; @@ -1281,6 +1287,7 @@ EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */, EAC58C0A2BED004E00BA39FA /* FieldType.swift in Sources */, EA471F3A2A95587500CE9E58 /* LayoutConstraintable.swift in Sources */, + EAC58C292BF4118C00BA39FA /* DatePickerViewController.swift in Sources */, EAB1D2CF28ABEF2B00DAE764 /* Typography+Base.swift in Sources */, EA0D1C3B2A6AD51B00E5C127 /* Typogprahy+Styles.swift in Sources */, EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */, @@ -1327,6 +1334,7 @@ EA3361B6288B2A410071C351 /* Control.swift in Sources */, EAC58C122BED0DDD00BA39FA /* Text.swift in Sources */, 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */, + EAC58C272BF4116200BA39FA /* DatePickerCalendarModel.swift in Sources */, EAF7F0B7289C12A600B287F5 /* UITapGestureRecognizer.swift in Sources */, 1842B1DF2BECE28B0021AFCA /* CalendarDateViewCell.swift in Sources */, EA0D1C392A6AD4DF00E5C127 /* Typography+SpacingConfig.swift in Sources */, diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 18465c47..1b1a797c 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -12,7 +12,7 @@ import Combine /// A calendar is a monthly view that lets customers select a single date. @objc(VDSCalendar) -open class CalendarBase: View { +open class CalendarBase: Control, Changeable { //-------------------------------------------------- // MARK: - Initializers @@ -32,6 +32,8 @@ open class CalendarBase: View { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- + open var onChangeSubscriber: AnyCancellable? + /// If set to true, the calendar will not have a border. open var hideContainerBorder: Bool = false { didSet { setNeedsUpdate() } } @@ -61,10 +63,7 @@ open class CalendarBase: View { /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. open var indicators: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } - - /// A callback when the date changes. Passes parameters (selectedDate). - public var onChangeSelectedDate: ((Date) -> Void)? - + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -326,7 +325,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI // Callback to pass selected date if it is enabled only. selectedDate = dates[indexPath.row] - onChangeSelectedDate?(selectedDate) + sendActions(for: .valueChanged) displayDate = selectedDate var reloadIndexPaths = [indexPath] diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 710de91a..ff9fbb06 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -5,7 +5,7 @@ import Combine /// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection. @objc(VDSDatePicker) -open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, UIPopoverPresentationControllerDelegate { +open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopoverPresentationControllerDelegate { //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -52,9 +52,7 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, open var selectedDate: Date? { didSet { setNeedsUpdate() } } - open var calendarModel: CalendarModel = .init() { - didSet { setNeedsUpdate() } - } + open var calendarModel: CalendarModel = .init() { didSet { setNeedsUpdate() } } open override var value: String? { get { selectedDateLabel.text } @@ -158,8 +156,7 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, } internal func togglePicker() { - //let calendarVC = DatePickerPopoverViewController(calendar: Calendar(identifier: .gregorian), delegate: self) - let calendarVC = DatePickerPopoverViewController(calendarModel, delegate: self) + let calendarVC = DatePickerViewController(calendarModel, delegate: self) calendarVC.modalPresentationStyle = .popover calendarVC.selectedDate = selectedDate ?? Date() if let popoverController = calendarVC.popoverPresentationController { @@ -173,7 +170,7 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, } } - internal func didSelectDate(_ controller: DatePickerPopoverViewController, date: Date) { + internal func didSelectDate(_ controller: DatePickerViewController, date: Date) { selectedDate = date controller.dismiss(animated: true) sendActions(for: .valueChanged) @@ -183,161 +180,3 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, return .none } } - -extension DatePicker { - public struct CalendarModel { - public let surface: Surface - - /// If set to true, the calendar will not have a border. - public let hideContainerBorder: Bool - - /// If set to true, the calendar will not have current date indication. - public let hideCurrentDateIndicator: Bool - - /// Enable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. - /// All other dates will be inactive. - public let activeDates: [Date] - - /// Disable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. - /// All other dates will be active. - public let inactiveDates: [Date] - - /// If provided, the calendar will allow a selection to be made from this date forward. Defaults to today. - public let minDate: Date - - /// If provided, the calendar will allow a selection to be made up to this date. - public let maxDate: Date - - /// If provided, this is the date that will show as selected by the Calendar. - /// If no value is provided, the current date will be used. If null is provided, no date will be selected. - public let selectedDate: Date - - /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. - public let indicators: [CalendarBase.CalendarIndicatorModel] - - public init(surface: Surface = .light, - hideContainerBorder: Bool = false, - hideCurrentDateIndicator: Bool = false, - selectedDate: Date = Date(), - activeDates: [Date] = [], - inactiveDates: [Date] = [], - minDate: Date = Date().startOfMonth, - maxDate: Date = Date().endOfMonth, - indicators: [CalendarBase.CalendarIndicatorModel] = []) { - self.surface = surface - self.hideContainerBorder = hideContainerBorder - self.hideCurrentDateIndicator = hideCurrentDateIndicator - self.selectedDate = selectedDate - self.activeDates = activeDates - self.inactiveDates = inactiveDates - self.minDate = minDate - self.maxDate = maxDate - self.indicators = indicators - } - } -} - -protocol DatePickerPopoverViewControllerDelegate: NSObject { - func didSelectDate(_ controller: DatePicker.DatePickerPopoverViewController, date: Date) -} - -//class DatePickerPopoverViewController: UIViewController { -// -// private let picker = UIDatePicker() -// weak var delegate: DatePickerPopoverViewControllerDelegate? -// -// init(calendar: Calendar, delegate: DatePickerPopoverViewControllerDelegate?) { -// self.delegate = delegate -// super.init(nibName: nil, bundle: nil) -// picker.datePickerMode = .date -// picker.preferredDatePickerStyle = .inline -// picker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) -// } -// -// var selectedDate: Date = Date() { -// didSet { -// picker.date = selectedDate -// } -// } -// -// required init?(coder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// override func viewDidLoad() { -// super.viewDidLoad() -// let v = UIView().with { -// $0.translatesAutoresizingMaskIntoConstraints = false -// $0.backgroundColor = .white -// $0.width(constant: 250) -// $0.height(constant: 350) -// } -// view.addSubview(v) -// v.pinTop(25).pinLeading(15).pinTrailing(15).pinBottom(15) -// -// view.backgroundColor = .blue -// -// preferredContentSize = CGSize(width: 300, height: 400) // Adjust as needed -// } -// -// @objc private func dateChanged(_ sender: UIDatePicker) { -// delegate?.didSelectDate(self, date: sender.date) -// } -//} -extension DatePicker { - class DatePickerPopoverViewController: UIViewController { - private var padding: CGFloat = 15 - private var topPadding: CGFloat { 10 + padding } - private var calendarModel: CalendarModel - private let picker = CalendarBase() - weak var delegate: DatePickerPopoverViewControllerDelegate? - - init(_ calendarModel: CalendarModel, delegate: DatePickerPopoverViewControllerDelegate?) { - self.delegate = delegate - self.calendarModel = calendarModel - super.init(nibName: nil, bundle: nil) - self.picker.onChangeSelectedDate = { [weak self] date in - guard let self else { return } - self.delegate?.didSelectDate(self, date: date) - } - } - - var selectedDate: Date = Date() { - didSet { - picker.selectedDate = selectedDate - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - view.addSubview(picker) - picker.surface = calendarModel.surface - picker.hideContainerBorder = calendarModel.hideContainerBorder - picker.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator - picker.activeDates = calendarModel.activeDates - picker.inactiveDates = calendarModel.inactiveDates - picker.selectedDate = calendarModel.selectedDate - picker.indicators = calendarModel.indicators - picker.minDate = calendarModel.minDate - picker.maxDate = calendarModel.maxDate - picker.pinToSuperView(.init(top: topPadding, left: padding, bottom: padding, right: padding)) - view.backgroundColor = picker.backgroundColor - } - - override var preferredContentSize: CGSize { - get { - var size = picker.containerSize - size.height += 40 - size.width += 30 - return size - } - set { - super.preferredContentSize = newValue - } - } - } -} diff --git a/VDS/Components/DatePicker/DatePickerCalendarModel.swift b/VDS/Components/DatePicker/DatePickerCalendarModel.swift new file mode 100644 index 00000000..b9b8c8f7 --- /dev/null +++ b/VDS/Components/DatePicker/DatePickerCalendarModel.swift @@ -0,0 +1,62 @@ +// +// DatePicker-CalendarModel.swift +// VDS +// +// Created by Matt Bruce on 5/14/24. +// + +import Foundation +import UIKit + +extension DatePicker { + public struct CalendarModel { + public let surface: Surface + + /// If set to true, the calendar will not have a border. + public let hideContainerBorder: Bool + + /// If set to true, the calendar will not have current date indication. + public let hideCurrentDateIndicator: Bool + + /// Enable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. + /// All other dates will be inactive. + public let activeDates: [Date] + + /// Disable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. + /// All other dates will be active. + public let inactiveDates: [Date] + + /// If provided, the calendar will allow a selection to be made from this date forward. Defaults to today. + public let minDate: Date + + /// If provided, the calendar will allow a selection to be made up to this date. + public let maxDate: Date + + /// If provided, this is the date that will show as selected by the Calendar. + /// If no value is provided, the current date will be used. If null is provided, no date will be selected. + public let selectedDate: Date + + /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. + public let indicators: [CalendarBase.CalendarIndicatorModel] + + public init(surface: Surface = .light, + hideContainerBorder: Bool = false, + hideCurrentDateIndicator: Bool = false, + selectedDate: Date = Date(), + activeDates: [Date] = [], + inactiveDates: [Date] = [], + minDate: Date = Date().startOfMonth, + maxDate: Date = Date().endOfMonth, + indicators: [CalendarBase.CalendarIndicatorModel] = []) { + self.surface = surface + self.hideContainerBorder = hideContainerBorder + self.hideCurrentDateIndicator = hideCurrentDateIndicator + self.selectedDate = selectedDate + self.activeDates = activeDates + self.inactiveDates = inactiveDates + self.minDate = minDate + self.maxDate = maxDate + self.indicators = indicators + } + } +} diff --git a/VDS/Components/DatePicker/DatePickerViewController.swift b/VDS/Components/DatePicker/DatePickerViewController.swift new file mode 100644 index 00000000..165dab75 --- /dev/null +++ b/VDS/Components/DatePicker/DatePickerViewController.swift @@ -0,0 +1,71 @@ +// +// DatePickerPopoverViewController.swift +// VDS +// +// Created by Matt Bruce on 5/14/24. +// + +import Foundation +import UIKit + +protocol DatePickerViewControllerDelegate: NSObject { + func didSelectDate(_ controller: DatePicker.DatePickerViewController, date: Date) +} + +extension DatePicker { + class DatePickerViewController: UIViewController { + private var padding: CGFloat = 15 + private var topPadding: CGFloat { 10 + padding } + private var calendarModel: CalendarModel + private let picker = CalendarBase() + weak var delegate: DatePickerViewControllerDelegate? + + init(_ calendarModel: CalendarModel, delegate: DatePickerViewControllerDelegate?) { + self.delegate = delegate + self.calendarModel = calendarModel + super.init(nibName: nil, bundle: nil) + self.picker.onChange = { [weak self] control in + guard let self else { return } + self.delegate?.didSelectDate(self, date: control.selectedDate) + } + } + + var selectedDate: Date = Date() { + didSet { + picker.selectedDate = selectedDate + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.addSubview(picker) + picker.surface = calendarModel.surface + picker.hideContainerBorder = calendarModel.hideContainerBorder + picker.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator + picker.indicators = calendarModel.indicators + picker.activeDates = calendarModel.activeDates + picker.inactiveDates = calendarModel.inactiveDates + picker.selectedDate = calendarModel.selectedDate + picker.minDate = calendarModel.minDate + picker.maxDate = calendarModel.maxDate + picker.pinToSuperView(.init(top: topPadding, left: padding, bottom: padding, right: padding)) + view.backgroundColor = picker.backgroundColor + } + + override var preferredContentSize: CGSize { + get { + var size = picker.frame.size + size.height += 40 + size.width += 30 + return size + } + set { + super.preferredContentSize = newValue + } + } + } +}