calendar.reloadData()
를 실행해야 하기 때문에 리소스 낭비가 심하다.사용할 calendar에 대해 기본적인 세팅을 한다
var calendar = FSCalendar()
calendar.scrollDirection = .vertical // 스크롤 방향
calendar.allowsMultipleSelection = true // 여러 날짜 선택 가능
calendar.register(SelectDatesCustomCalendarCell.self, forCellReuseIdentifier: SelectDatesCustomCalendarCell.description()) // 커스텀 셀 등록
calendar.appearance.titleFont = .boldSystemFont(ofSize: 18) // 날짜 표시 레이블 폰트 설정
calendar.appearance.headerTitleFont = .boldSystemFont(ofSize: 20) // 월 표시 레이블 폰트 설정
calendar.today = nil // 기본 오늘 선택 해제
calendar.appearance.selectionColor = .clear // 기본 선택 배경 투명 -> 커스텀 셀 배경으로 표시
calendar.appearance.caseOptions = .weekdayUsesSingleUpperCase // 요일 텍스트를 영어 한글자로 표시
// 기본 색상 선택
calendar.appearance.titleDefaultColor = .black
calendar.appearance.headerTitleColor = .black
calendar.appearance.weekdayTextColor = .black
calendar.appearance.titleSelectionColor = UIColor.appColor(.main1)
enum SelectedDateType {
case singleDate // 날짜 하나만 선택된 경우 (원 모양 배경)
case firstDate // 여러 날짜 선택 시 맨 처음 날짜
case middleDate // 여러 날짜 선택 시 맨 처음, 마지막을 제외한 중간 날짜
case lastDate // 여러 날짜 선택시 맨 마지막 날짜
case notSelectd // 선택되지 않은 날짜
}
class SelectDatesCustomCalendarCell: FSCalendarCell {
var circleBackImageView = UIImageView()
var leftRectBackImageView = UIImageView()
var rightRectBackImageView = UIImageView()
func setConfigure() {
contentView.insertSubview(circleBackImageView, at: 0)
contentView.insertSubview(leftRectBackImageView, at: 0)
contentView.insertSubview(rightRectBackImageView, at: 0)
}
func setConstraints() {
// 날짜 텍스트의 레이아웃을 센터로 잡아준다 (기본적으로 약간 위에 있다)
self.titleLabel.snp.makeConstraints { make in
make.center.equalTo(contentView)
}
leftRectBackImageView.snp.makeConstraints { make in
make.leading.equalTo(contentView)
make.trailing.equalTo(contentView.snp.centerX)
make.height.equalTo(46)
make.centerY.equalTo(contentView)
}
circleBackImageView.snp.makeConstraints { make in
make.center.equalTo(contentView)
make.size.equalTo(46)
}
rightRectBackImageView.snp.makeConstraints { make in
make.leading.equalTo(contentView.snp.centerX)
make.trailing.equalTo(contentView)
make.height.equalTo(46)
make.centerY.equalTo(contentView)
}
}
func settingImageView() {
circleBackImageView.clipsToBounds = true
circleBackImageView.layer.cornerRadius = 23
// 선택 날짜의 배경 색상을 여기서 정한다.
[circleBackImageView, leftRectBackImageView, rightRectBackImageView].forEach { item in
item.backgroundColor = UIColor.appColor(.main3)
}
}
}
class SelectDatesCustomCalendarCell: FSCalendarCell {
func updateBackImage(_ dateType: SelectedDateType) {
switch dateType {
case .singleDate:
// left right hidden true
// circle hidden false
leftRectBackImageView.isHidden = true
rightRectBackImageView.isHidden = true
circleBackImageView.isHidden = false
case .firstDate:
// leftRect hidden true
// circle, right hidden false
leftRectBackImageView.isHidden = true
circleBackImageView.isHidden = false
rightRectBackImageView.isHidden = false
case .middleDate:
// circle hidden true
// left, right hidden false
circleBackImageView.isHidden = true
leftRectBackImageView.isHidden = false
rightRectBackImageView.isHidden = false
case .lastDate:
// rightRect hidden true
// circle, left hidden false
rightRectBackImageView.isHidden = true
circleBackImageView.isHidden = false
leftRectBackImageView.isHidden = false
case .notSelectd:
// all hidden
circleBackImageView.isHidden = true
leftRectBackImageView.isHidden = true
rightRectBackImageView.isHidden = true
}
}
}
class SelectedDateViewController: UIViewController {
func settingCalendar() {
mainView.calendar.delegate = self
mainView.calendar.dataSource = self
}
}
class SelectedDateViewController: UIViewController {
private var firstDate: Date? // 배열 중 첫번째 날짜
private var lastDate: Date? // 배열 중 마지막 날짜
private var datesRange: [Date] = [] // 선택된 날짜 배열
}
extension SelectedDateViewController: FSCalendarDataSource {
// 매개변수로 들어온 date의 타입을 반환한다
func typeOfDate(_ date: Date) -> SelectedDateType {
let arr = datesRange
if !arr.contains(date) {
return .notSelectd // 배열이 비어있으면 무조건 notSelected
}
else {
// 배열의 count가 1이고, firstDate라면 singleDate
if arr.count == 1 && date == firstDate { return .singleDate }
// 배열의 count가 2 이상일 때, 각각 타입 반환
if date == firstDate { return .firstDate }
if date == lastDate { return .lastDate }
else { return .middleDate }
}
}
func calendar(_ calendar: FSCalendar, cellFor date: Date, at position: FSCalendarMonthPosition) -> FSCalendarCell {
guard let cell = calendar.dequeueReusableCell(withIdentifier: SelectDatesCustomCalendarCell.description(), for: date, at: position) as? SelectDatesCustomCalendarCell else { return FSCalendarCell() }
// 현재 그리는 셀의 date의 타입에 기반해서 셀 디자인
cell.updateBackImage(typeOfDate(date))
return cell
}
}
extension SelectedDateViewController: FSCalendarDelegate {
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
// case 1. 현재 아무것도 선택되지 않은 경우
// 선택 date -> firstDate 설정
if firstDate == nil {
firstDate = date
datesRange = [firstDate!]
mainView.calendar.reloadData() // (매번 reload)
return
}
// case 2. 현재 firstDate 하나만 선택된 경우
if firstDate != nil && lastDate == nil {
// case 2 - 1. firstDate 이전 날짜 선택 -> firstDate 변경
if date < firstDate! {
calendar.deselect(firstDate!)
firstDate = date
datesRange = [firstDate!]
mainView.calendar.reloadData() // (매번 reload)
return
}
// case 2 - 2. firstDate 이후 날짜 선택 -> 범위 선택
else {
var range: [Date] = []
var currentDate = firstDate!
while currentDate <= date {
range.append(currentDate)
currentDate = Calendar.current.date(byAdding: .day, value: 1, to: currentDate)!
}
for day in range {
calendar.select(day)
}
lastDate = range.last
datesRange = range
mainView.calendar.reloadData() // (매번 reload)
return
}
}
// case 3. 두 개가 모두 선택되어 있는 상태 -> 현재 선택된 날짜 모두 해제 후 선택 날짜를 firstDate로 설정
if firstDate != nil && lastDate != nil {
for day in calendar.selectedDates {
calendar.deselect(day)
}
lastDate = nil
firstDate = date
calendar.select(date)
datesRange = [firstDate!]
mainView.calendar.reloadData() // (매번 reload)
return
}
}
}
extension SelectDateViewController: FSCalendarDelegate {
// 이미 선택된 날짜들 중 하나를 선택 -> 선택된 날짜 모두 초기화
func calendar(_ calendar: FSCalendar, didDeselect date: Date, at monthPosition: FSCalendarMonthPosition) {
let arr = datesRange
if !arr.isEmpty {
for day in arr {
calendar.deselect(day)
}
}
firstDate = nil
lastDate = nil
datesRange = []
mainView.calendar.reloadData() // (매번 reload)
}