How to Select Range - FSCalendar in Swift?

Although FSCalendar does not directly supports range selection, it does allow multiple selection, which means that at some point you would be able to handle the range selection by your self.

So, in the viewDidLoad() you have to make sure that you set the calendar allowsMultipleSelection property to true:

private weak var calendar: FSCalendar!

override func viewDidLoad() {
    super.viewDidLoad()

    calendar.allowsMultipleSelection = true
}

The view controller should conforms to FSCalendarDelegate protocol for handling the logic of selecting/deselecting a range.

What we need so far is declare tow dates for the range (the staring date and the ending date):

// first date in the range
private var firstDate: Date?
// last date in the range
private var lastDate: Date?

also an array of dates to hold value dates between firstDate and lastDate:

private var datesRange: [Date]?

and then implement the didSelect date and the didDeselect date delegate methods as:

func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
    // nothing selected:
    if firstDate == nil {
        firstDate = date
        datesRange = [firstDate!]

        print("datesRange contains: \(datesRange!)")

        return
    }

    // only first date is selected:
    if firstDate != nil && lastDate == nil {
        // handle the case of if the last date is less than the first date:
        if date <= firstDate! {
            calendar.deselect(firstDate!)
            firstDate = date
            datesRange = [firstDate!]

            print("datesRange contains: \(datesRange!)")

            return
        }

        let range = datesRange(from: firstDate!, to: date)

        lastDate = range.last

        for d in range {
            calendar.select(d)
        }

        datesRange = range

        print("datesRange contains: \(datesRange!)")

        return
    }

    // both are selected:
    if firstDate != nil && lastDate != nil {
        for d in calendar.selectedDates {
            calendar.deselect(d)
        }

        lastDate = nil
        firstDate = nil

        datesRange = []

        print("datesRange contains: \(datesRange!)")
    }
}

func calendar(_ calendar: FSCalendar, didDeselect date: Date, at monthPosition: FSCalendarMonthPosition) {
    // both are selected:

    // NOTE: the is a REDUANDENT CODE:
    if firstDate != nil && lastDate != nil {
        for d in calendar.selectedDates {
            calendar.deselect(d)
        }

        lastDate = nil
        firstDate = nil

        datesRange = []
        print("datesRange contains: \(datesRange!)")
    }
}

What about datesRange method?

I did not mentioned it in my answer for the purpose of making it shorter, all you have to do is to copy-paste it from this answer.

Output:

enter image description here


class CalendarDelegate: NSObject, FSCalendarDelegate {
  
    func calendar(_ calendar: FSCalendar, shouldDeselect date: Date, at monthPosition: FSCalendarMonthPosition) -> Bool {
        performDateDeselect(calendar, date: date)
        return true
    }
    
    func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
        performDateSelection(calendar)
    }
    
    private func performDateDeselect(_ calendar: FSCalendar, date: Date) {
        let sorted = calendar.selectedDates.sorted { $0 < $1 }
        if let index = sorted.firstIndex(of: date)  {
            let deselectDates = Array(sorted[index...])
            calendar.deselectDates(deselectDates)
        }
    }
    
    private func performDateSelection(_ calendar: FSCalendar) {
        let sorted = calendar.selectedDates.sorted { $0 < $1 }
        if let firstDate = sorted.first, let lastDate = sorted.last {
            let ranges = datesRange(from: firstDate, to: lastDate)
            calendar.selectDates(ranges)
        }
    }
    
    func datesRange(from: Date, to: Date) -> [Date] {
        if from > to { return [Date]() }
        var tempDate = from
        var array = [tempDate]
        while tempDate < to {
            tempDate = Calendar.current.date(byAdding: .day, value: 1, to: tempDate)!
            array.append(tempDate)
        }
        return array
    }
}