// // Calendar.swift // VDS // // Created by Kanamarlapudi, Vasavi on 19/04/24. // import Foundation import UIKit import VDSTokens import Combine /// A calendar is a monthly view that lets customers select a single date. @objc(VDSCalendar) open class CalendarBase: View { //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- required public init() { super.init(frame: .zero) } public override init(frame: CGRect) { super.init(frame: .zero) } public required init?(coder: NSCoder) { super.init(coder: coder) } //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- /// If set to true, the calendar will not have a border. open var hideContainerBorder: Bool = false /// If set to true, the calendar will not have current date indication. open var hideCurrentDateIndicator: Bool = false /// 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 open var 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. open var inactiveDates: [Date] = [] /// If provided, the calendar will allow a selection to be made from this date forward. Defaults to today. open var minDate: Date? /// If provided, the calendar will allow a selection to be made up to this date. open var 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. open var selectedDate: Date? /// If provided, the calendar will be rendered with transparent background. open var transparentBackground: Bool = false /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. open var indicators: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } // /// Array of indicators for the legend. // open var indicatorData: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } /// A callback when the selected date changes.. open var onChangeSelectedDate: ((Date) -> String)? //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- internal var containerSize: CGSize { CGSize(width: 320, height: 376) } //width:320/328 private let cellItemSize = CGSize(width: 40, height: 40) private let headerHeight = 104.0 private let footerHeight = 56.0 private let items = 35 internal var containerView = View().with { $0.clipsToBounds = true } /// Collectionview to load calendar month view private lazy var collectionView: UICollectionView = { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) collectionView.isScrollEnabled = false collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.delegate = self collectionView.dataSource = self collectionView.showsHorizontalScrollIndicator = false collectionView.showsVerticalScrollIndicator = false collectionView.register(CalendarDateCollectionViewCell.self, forCellWithReuseIdentifier: CalendarDateCollectionViewCell.identifier) collectionView.register(CalendarHeaderReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: CalendarHeaderReusableView.identifier) collectionView.register(CalendarFooterReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: CalendarFooterReusableView.identifier) return collectionView }() //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open override func initialSetup() { super.initialSetup() } open override func setup() { super.setup() isAccessibilityElement = false addSubview(containerView) containerView .pinTop() .pinBottom() .pinLeadingGreaterThanOrEqualTo() .pinTrailingLessThanOrEqualTo() .height(containerSize.height) .width(containerSize.width) containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() // Calendar View containerView.addSubview(collectionView) collectionView.pinToSuperView() } open override func updateView() { super.updateView() collectionView.reloadData() } override open func layoutSubviews() { super.layoutSubviews() } open override func reset() { super.reset() } } extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { //-------------------------------------------------- // MARK: - UICollectionView Delegate & Datasource //-------------------------------------------------- public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { items } public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CalendarDateCollectionViewCell.identifier, for: indexPath) as? CalendarDateCollectionViewCell else { return UICollectionViewCell() } return cell } public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { if kind == UICollectionView.elementKindSectionHeader { // Header guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarHeaderReusableView.identifier, for: indexPath) as? CalendarHeaderReusableView else { return UICollectionReusableView() } header.configure(with: true) return header } else { // Footer if kind == UICollectionView.elementKindSectionFooter { guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarFooterReusableView.identifier, for: indexPath) as? CalendarFooterReusableView else { return UICollectionReusableView() } footer.configure(with: indicators) return footer } } return UICollectionReusableView() } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { return CGSize(width: collectionView.frame.size.width, height: headerHeight) } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { return CGSize(width: collectionView.frame.size.width, height: footerHeight) } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return VDSLayout.space1X } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return VDSLayout.space1X } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return cellItemSize } }