patchthecode / JTAppleCalendar

The Unofficial Apple iOS Swift Calendar View. Swift calendar Library. iOS calendar Control. 100% Customizable
https://patchthecode.com
MIT License
7.53k stars 802 forks source link

Future date is not getting disabled in a single selection as well as in range selection #1401

Open dimpy-iroid opened 4 weeks ago

dimpy-iroid commented 4 weeks ago

(Required) Version Number: 8.0.5 Screenshot 2024-06-11 at 5 02 50 PM

Description

I have to disable the future dates in two calendars in which dailyCalendarView has single selection and weeklyCalendarView has range selection.

but the issue is sometimes future dates are not getting disabled. Also, how do I allow users to select only 7 dates in weeklyCalendarView? @IBAction func onClear(_ sender: UIButton) { self.dailyCalendarView.deselectAllDates() self.weeklyCalendarView.deselectAllDates() } this code is also not working.

https://github.com/patchthecode/JTAppleCalendar/assets/130362429/7c1fae36-6f3e-49d2-a916-498fd6b637f6

https://github.com/patchthecode/JTAppleCalendar/assets/130362429/76f04525-7415-40eb-827b-ba6a835a1c47

Steps To Reproduce

` import UIKit import JTAppleCalendar

class InsightsFilterScreen: BottomPopupViewController { // MARK: - Outlets //UIButton @IBOutlet weak var dailyMonthButton: UIButton! @IBOutlet weak var dailyYearButton: UIButton! @IBOutlet weak var weeklyMonthButton: UIButton! @IBOutlet weak var weeklyYearButton: UIButton!

//UIView
@IBOutlet weak var contentView: UIView!
@IBOutlet weak var dailyView: UIView!
@IBOutlet weak var weeklyView: UIView!
@IBOutlet weak var monthlyView: UIView!

//JTACMonthView
@IBOutlet weak var dailyCalendarView: JTACMonthView!
@IBOutlet weak var weeklyCalendarView: JTACMonthView!

//UISegmentedControl
@IBOutlet weak var insightsSegmentedControl: UISegmentedControl!

//UILabel
@IBOutlet weak var yearLabel: UILabel!

//UICollectionView
@IBOutlet weak var yearCollectionView: UICollectionView!

// MARK: BottomPopupAttributesDelegate Variables
//Bottom Popup Alert configurations
var height: CGFloat?
var topCornerRadius: CGFloat?
var presentDuration: Double?
var dismissDuration: Double?
var shouldDismissInteractivelty: Bool?
var dimmingViewDefaultAlphaValue: CGFloat?

override var popupHeight: CGFloat { height ?? SCREEN_HEIGHT }
override var popupTopCornerRadius: CGFloat { topCornerRadius ?? 0.0 } //10.0
override var popupPresentDuration: Double { presentDuration ?? 0.4 }
override var popupDismissDuration: Double { dismissDuration ?? 0.4 } // 1.0
override var popupShouldDismissInteractivelty: Bool { shouldDismissInteractivelty ?? false }
override var popupDimmingViewAlpha: CGFloat { dimmingViewDefaultAlphaValue ?? 0.30 }

// MARK: - Variables
var hasStrictBoundaries = true
var generateInDates: InDateCellGeneration = .forAllMonths
var generateOutDates: OutDateCellGeneration = .off
var allScrollModes: [ScrollingMode] = [
    .none,
    .nonStopTo(customInterval: 374, withResistance: 0.5),
    .nonStopToCell(withResistance: 0.5),
    .nonStopToSection(withResistance: 0.5),
    .stopAtEach(customInterval: 374),
    .stopAtEachCalendarFrame,
    .stopAtEachSection

]

// MARK: - Daily
var dailyCalendar = Calendar.current
var dailyCurrentScrollModeIndex = 0
var dailyStartSelectedNSDate = Date()
var dailySelectedDatesArray:[String] = []
var dailyStartSelectedDate = "2020 01 01"
let dailyFormatter = DateFormatter()

// MARK: - Week
var weekCalendar = Calendar.current
var weekCurrentScrollModeIndex = 0
var weekStartSelectedNSDate = Date()
var weekSelectedDatesArray:[String] = []
var weekStartSelectedDate = "2020 01 01"
let weekFormatter = DateFormatter()

// MARK: - Methods
override func viewDidLoad() {
    super.viewDidLoad()
    self.initialDetails()
}

// MARK: - Functions
// MARK: - Initial Details
func initialDetails() {
    self.contentView.roundCorners([.topLeft, .topRight], radius: 20.0)
    self.setUpSegmentControl()
    self.setUpWeeklyCalendar()
    self.setUpDailyCalendar()
}

// MARK: - Set Up Daily Calendar
func setUpDailyCalendar() {
    self.dailyCalendarView.calendarDelegate = self
    self.dailyCalendarView.calendarDataSource = self
    self.dailyCalendarView.scrollDirection = .horizontal
    self.dailyCalendarView.allowsMultipleSelection = false
    self.dailyCalendarView.scrollToDate(Date(),animateScroll: false)
    //        self.calendarCollection.selectDates([Date()])

    self.dailyCalendarView.visibleDates {[unowned self] (visibleDates: DateSegmentInfo) in
        self.setupViewsOfDailyCalendar(from: visibleDates)
    }

    self.setupScrollModeForDailyCalendar()
}

// MARK: - Set Up Views of Daily Calendar
func setupViewsOfDailyCalendar(from visibleDates: DateSegmentInfo) {
    guard let startDate = visibleDates.monthDates.first?.date else {
        return
    }
    let month = self.dailyCalendar.dateComponents([.month], from: startDate).month!
    // let monthName = DateFormatter().monthSymbols[(month-1) % 12]

    let monthName = self.dailyCalendar.monthSymbols[month - 1]
    // 0 indexed array
    let year = self.dailyCalendar.component(.year, from: startDate)
    self.dailyMonthButton.setTitle(monthName, for: .normal)
    self.dailyYearButton.setTitle(String(year), for: .normal)
}

// MARK: - Set Up Scroll Mode for Daily Calendar
func setupScrollModeForDailyCalendar() {
    self.dailyCurrentScrollModeIndex = 6
    self.dailyCalendarView.scrollingMode = self.allScrollModes[self.dailyCurrentScrollModeIndex]
}

// MARK: - Set Up Weekly Calendar
func setUpWeeklyCalendar() {
    self.weeklyCalendarView.calendarDelegate = self
    self.weeklyCalendarView.calendarDataSource = self
    self.weeklyCalendarView.scrollDirection = .horizontal
    self.weeklyCalendarView.allowsMultipleSelection = true
    self.weeklyCalendarView.allowsRangedSelection = true
    self.weeklyCalendarView.rangeSelectionMode = .continuous
    self.weeklyCalendarView.scrollToDate(Date(),animateScroll: false)
    //        self.calendarCollection.selectDates([Date()])

    self.weeklyCalendarView.visibleDates {[unowned self] (visibleDates: DateSegmentInfo) in
        self.setupViewsOfWeeklyCalendar(from: visibleDates)
    }

    self.setupScrollModeForWeeklyCalendar()
}

// MARK: - Set Up Views of Weekly Calendar
func setupViewsOfWeeklyCalendar(from visibleDates: DateSegmentInfo) {
    guard let startDate = visibleDates.monthDates.first?.date else {
        return
    }
    let month = self.weekCalendar.dateComponents([.month], from: startDate).month!
    // let monthName = DateFormatter().monthSymbols[(month-1) % 12]

    let monthName = self.weekCalendar.monthSymbols[month - 1]
    // 0 indexed array
    let year = self.weekCalendar.component(.year, from: startDate)
    self.weeklyMonthButton.setTitle(monthName, for: .normal)
    self.weeklyYearButton.setTitle(String(year), for: .normal)
}

// MARK: - Set Up Scroll Mode for Weekly Calendar
func setupScrollModeForWeeklyCalendar() {
    self.weekCurrentScrollModeIndex = 6
    self.weeklyCalendarView.scrollingMode = self.allScrollModes[self.weekCurrentScrollModeIndex]
}

// MARK: - Set up Segment Control
func setUpSegmentControl() {
    self.insightsSegmentedControl.selectedSegmentIndex = 1
    //set color
    UISegmentedControl.appearance().setTitleTextAttributes(
        [.foregroundColor: UIColor { tc in
            switch tc.userInterfaceStyle {
            case .dark:
                return UtilityMethodsCommon.hexStringToUIColor(hex: "#FCF4F4")
            default:
                return UtilityMethodsCommon.hexStringToUIColor(hex: "#141414")
            }
        }
        ], for: .normal)
    UISegmentedControl.appearance().setTitleTextAttributes(
        [.foregroundColor: UIColor { tc in
            switch tc.userInterfaceStyle {
            case .dark:
                return UtilityMethodsCommon.hexStringToUIColor(hex: "#212121")
            default:
                return UIColor.white
            }
        }
        ], for: .selected)

    //set font
    UISegmentedControl.appearance().setTitleTextAttributes([.font: UIFont(name: "Poppins-Regular", size: 15) ?? UIFont()], for: .normal)
}

// MARK: - IBActions
@IBAction func onBack(_ sender: UIButton) {
    self.dismiss(animated: true)
}

@IBAction func onClear(_ sender: UIButton) {
    self.dailyCalendarView.deselectAllDates()
    self.weeklyCalendarView.deselectAllDates()
}

@IBAction func onSegment(_ sender: UISegmentedControl) {
    self.onSegmentControl()
}

@IBAction func onDailyMonth(_ sender: UIButton) {
}

@IBAction func onDailyYear(_ sender: UIButton) {
}

@IBAction func onWeeklyMonth(_ sender: UIButton) {
}

@IBAction func onWeeklyYear(_ sender: UIButton) {
}

//Daily
@IBAction func onDailySave(_ sender: UIButton) {
}

//Monthly
@IBAction func onMonthlySave(_ sender: UIButton) {
}

@IBAction func onPrevious(_ sender: UIButton) {
}

@IBAction func onNext(_ sender: UIButton) {
}

@IBAction func onYearlySave(_ sender: UIButton) {
}

// MARK: - Segment Control Action
func onSegmentControl() {
    let views = [self.dailyView, self.weeklyView, self.monthlyView]
    let selectedIndex = self.insightsSegmentedControl.selectedSegmentIndex
    for (index, view) in views.enumerated() {
        view?.isHidden = index != selectedIndex
    }
}

}

// MARK: - JTACMonthView DataSource extension InsightsFilterScreen: JTACMonthViewDataSource { func configureCalendar(_ calendar: JTACMonthView) -> ConfigurationParameters {

    switch calendar {
    case self.dailyCalendarView:
        self.dailyFormatter.dateFormat = "yyyy-MM-dd"
        self.dailyFormatter.timeZone = self.dailyCalendar.timeZone
        self.dailyFormatter.locale = self.dailyCalendar.locale

        let startDate = self.dailyFormatter.date(from: "2023-01-01")!
        let endDate = Date()

        let parameters = ConfigurationParameters(startDate: startDate,
                                                 endDate: endDate,
                                                 numberOfRows: 6,
                                                 calendar: self.dailyCalendar,
                                                 generateInDates: generateInDates,
                                                 generateOutDates: generateOutDates,
                                                 firstDayOfWeek: .sunday,
                                                 hasStrictBoundaries: true)
        return parameters

    case self.weeklyCalendarView:
        self.weekFormatter.dateFormat = "yyyy-MM-dd"
        self.weekFormatter.timeZone = self.weekCalendar.timeZone
        self.weekFormatter.locale = self.weekCalendar.locale

        let startDate = self.weekFormatter.date(from: "2023-01-01")!
        let endDate = Date()

        let parameters = ConfigurationParameters(startDate: startDate,
                                                 endDate: endDate,
                                                 numberOfRows: 6,
                                                 calendar: self.weekCalendar,
                                                 generateInDates: generateInDates,
                                                 generateOutDates: generateOutDates,
                                                 firstDayOfWeek: .sunday,
                                                 hasStrictBoundaries: true)
        return parameters
    default:
        break
    }
    return ConfigurationParameters(startDate: Date(), endDate: Date())
}

}

// MARK: - JTACMonthView Delegate extension InsightsFilterScreen: JTACMonthViewDelegate { func calendar(_ calendar: JTACMonthView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTACDayCell {

    switch calendar {
    case self.dailyCalendarView:
        if let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "DailyDateCell", for: indexPath) as? DailyDateCell {
            cell.cellView.clipsToBounds = true
            cell.cellView.layer.cornerRadius = cell.cellView.frame.height/2
            cell.dateLabel.text = cellState.text
            if self.dailyFormatter.string(from: Date()) < self.dailyFormatter.string(from: date) {
                cell.dateLabel.textColor = .black.withAlphaComponent(0.5)
            } else {
                cell.dateLabel.textColor = .black
            }
            self.calendar(calendar, willDisplay: cell, forItemAt: date, cellState: cellState, indexPath: indexPath)
            return cell
        }
        return JTACDayCell()
    case self.weeklyCalendarView:
        if let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "WeeklyDateCell", for: indexPath) as? WeeklyDateCell {
            cell.cellView.clipsToBounds = true
            cell.cellView.layer.cornerRadius = cell.cellView.frame.height/2
            cell.dateLabel.text = cellState.text
            if self.weekFormatter.string(from: Date()) < self.weekFormatter.string(from: date) {
                cell.dateLabel.textColor = .black.withAlphaComponent(0.5)
            } else {
                cell.dateLabel.textColor = .black
            }
            self.calendar(calendar, willDisplay: cell, forItemAt: date, cellState: cellState, indexPath: indexPath)
            return cell
        }
        return JTACDayCell()
    default:
        break
    }
    return JTACDayCell()
}

func calendar(_ calendar: JTACMonthView, willDisplay cell: JTACDayCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) {
    switch calendar {
    case self.dailyCalendarView:
        let myCustomCell = cell as! DailyDateCell
        self.configureVisibleDailyCell(myCustomCell: myCustomCell, cellState: cellState, date: date, indexPath: indexPath)

        if cellState.dateBelongsTo == .thisMonth {
            myCustomCell.isHidden = false
        } else {
            myCustomCell.isHidden = true
        }
    case self.weeklyCalendarView:
        let myCustomCell = cell as! WeeklyDateCell
        self.configureVisibleWeeklyCell(myCustomCell: myCustomCell, cellState: cellState, date: date, indexPath: indexPath)

        if cellState.dateBelongsTo == .thisMonth {
            myCustomCell.isHidden = false
        } else {
            myCustomCell.isHidden = true
        }
    default:
        break
    }
}

func calendar(_ calendar: JTACMonthView, willScrollToDateSegmentWith visibleDates: DateSegmentInfo) {
    switch calendar {
    case self.dailyCalendarView:
        self.setupViewsOfDailyCalendar(from: visibleDates)
    case self.weeklyCalendarView:
        self.setupViewsOfWeeklyCalendar(from: visibleDates)
    default:
        break
    }
}

func calendar(_ calendar: JTACMonthView, didScrollToDateSegmentWith visibleDates: DateSegmentInfo) {
    switch calendar {
    case self.dailyCalendarView:
        self.setupViewsOfDailyCalendar(from: visibleDates)
    case self.weeklyCalendarView:
        self.setupViewsOfWeeklyCalendar(from: visibleDates)
    default:
        break
    }
}

func calendar(_ calendar: JTACMonthView, shouldSelectDate date: Date, cell: JTACDayCell?, cellState: CellState) -> Bool {
    return date < Date()
}

func calendar(_ calendar: JTACMonthView, didSelectDate date: Date, cell: JTACDayCell?, cellState: CellState) {
    switch calendar {
    case self.dailyCalendarView:
        self.handleCellSelectionForDaily(view: cell, cellState: cellState)
    case self.weeklyCalendarView:
        self.handleCellSelectionForWeekly(view: cell, cellState: cellState)
    default:
        break
    }
}

func calendar(_ calendar: JTACMonthView, didDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState) {
    switch calendar {
    case self.dailyCalendarView:
        self.handleCellSelectionForDaily(view: cell, cellState: cellState)
    case self.weeklyCalendarView:
        self.handleCellSelectionForWeekly(view: cell, cellState: cellState)
    default:
        break
    }
}
func calendar(_ calendar: JTACMonthView, didDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {
    if self.dailyView.isHidden == false {
        if self.dailyFormatter.string(from: Date()) < self.dailyFormatter.string(from: date) {
            return
        }
    } else if self.weeklyView.isHidden == false {
        if self.weekFormatter.string(from: Date()) < self.weekFormatter.string(from: date) {
            return
        }
    }

    switch calendar {
    case self.dailyCalendarView:
        self.addOrRemove(value: self.dailyFormatter.string(from: date), in: &dailySelectedDatesArray)
        self.dailyCalendarView.reloadData()
        self.handleCellSelectionForDaily(view: cell, cellState: cellState)
    case self.weeklyCalendarView:
        self.addOrRemove(value: self.weekFormatter.string(from: date), in: &weekSelectedDatesArray)
        self.weeklyCalendarView.reloadData()
        self.handleCellSelectionForWeekly(view: cell, cellState: cellState)
    default:
        break
    }
}

func calendar(_ calendar: JTACMonthView, didSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {
    if self.dailyView.isHidden == false {
        if self.dailyFormatter.string(from: Date()) < self.dailyFormatter.string(from: date) {
            return
        }
    } else if self.weeklyView.isHidden == false {
        if self.weekFormatter.string(from: Date()) < self.weekFormatter.string(from: date) {
            return
        }
    }

    switch calendar {
    case self.dailyCalendarView:
        self.dailyStartSelectedNSDate = date
        self.dailyStartSelectedDate = self.dailyFormatter.string(from: date)
        self.addOrRemove(value: self.dailyFormatter.string(from: date), in: &dailySelectedDatesArray)

        self.dailyCalendarView.reloadData()
        self.handleCellSelectionForDaily(view: cell, cellState: cellState)

if DEBUG

        print("dailyFormatter --> \(self.dailyFormatter.string(from: date))")

endif

    case self.weeklyCalendarView:
        self.weekStartSelectedNSDate = date
        self.weekStartSelectedDate = self.weekFormatter.string(from: date)
        self.addOrRemove(value: self.weekFormatter.string(from: date), in: &weekSelectedDatesArray)

        self.weeklyCalendarView.reloadData()
        self.handleCellSelectionForWeekly(view: cell, cellState: cellState)

if DEBUG

        print("weekFormatter --> \(self.weekFormatter.string(from: date))")

endif

    default:
        break
    }
}

func handleCellSelectionForWeekly(view: JTACDayCell?, cellState: CellState) {
    guard let cell = view as? WeeklyDateCell  else {
        return
    }

    cell.cellView.isHidden = !cellState.isSelected

    switch cellState.selectedPosition() {
    case .left:
        cell.cellView.layer.cornerRadius = cell.cellView.frame.height/2
        cell.cellView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMinXMinYCorner]
    case .middle:
        cell.cellView.layer.cornerRadius = 0
        cell.cellView.layer.maskedCorners = []
    case .right:
        cell.cellView.layer.cornerRadius = cell.cellView.frame.height/2
        cell.cellView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner]
    case .full:
        cell.cellView.layer.cornerRadius = cell.cellView.frame.height/2
        cell.cellView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]
    default: break
    }
}

func handleCellSelectionForDaily(view: JTACDayCell?, cellState: CellState) {
    guard let myCustomCell = view as? DailyDateCell  else {
        return
    }

    if cellState.isSelected {
        myCustomCell.cellView.backgroundColor = #colorLiteral(red: 0.2901960784, green: 0.3098039216, blue: 0.968627451, alpha: 1)
    } else {
        myCustomCell.cellView.backgroundColor = .clear
    }

    myCustomCell.isHidden = cellState.dateBelongsTo != .thisMonth
}

func configureVisibleDailyCell(myCustomCell: DailyDateCell, cellState: CellState, date: Date, indexPath: IndexPath) {
    handleCellSelectionForDaily(view: myCustomCell, cellState: cellState)
}

func configureVisibleWeeklyCell(myCustomCell: WeeklyDateCell, cellState: CellState, date: Date, indexPath: IndexPath) {
    handleCellSelectionForWeekly(view: myCustomCell, cellState: cellState)
}

func addOrRemove<T: Equatable>(value: T, in array: inout [T]) {
    if let index = array.firstIndex(of: value) {
        array.remove(at: index)
    } else {
        array.append(value)
    }
}

} `

Expected Behavior

I want to disable future dates. I want to allow users to select only 7 dates in weeklyCalendarView. I want to have a start date and end date in range selection like this: image

on clear button all the selected dates should get deselected at one time.

Additional Context

I am constantly trying from the last two days to resolve this issue, Also there is no documentation here on how to create your custom calendar. If anyone could guide me I would be really grateful.

Thank you!

patchthecode commented 4 weeks ago

Its difficult to read this. Is your project sharable?

patchthecode commented 4 weeks ago

Also have you checked this out? https://github.com/patchthecode/JTAppleCalendar/issues/1319

dimpy-iroid commented 4 weeks ago

Hello! Thank you for your response, yes I have checked almost all the issues in the last 2 days. But I'm not able to fix the issue. Future dates are getting selected in some cases. Could you please see attached zip file of my project? CalendarApp.zip

Thank you! Kindly see Expected Behaviour mentioned above.