🍎 iOS/iOS 심화 & 응용

[iOS/Swift] FSCalendar로 캘린더 구현하기

dev_zoe 2022. 12. 15. 18:38
반응형

🔥 목표는 위 캘린더를 구현하는것!

- 저는 Snapkit으로 구현하였습니다.

 

1. FSCalendar 뷰 생성 및 delegate, datasource 선언

private var calendar = FSCalendar()

//viewDidLoad()
calendar.delegate = self
calendar.dataSource = self

 

2. 달력 기본값 세팅

        calendar.scope = .month //월 표시
        calendar.locale = Locale(identifier: "ko_KR") //요일을 한글로 표시하기 위함

 

3. 헤더 - 기본 설정

        calendar.appearance.headerDateFormat = "YYYY년 MM월" //헤더 포맷
        calendar.appearance.headerTitleColor = .black //헤더 색 설정
        calendar.appearance.headerTitleFont = .pretendardMedium16 //헤더 폰트
        calendar.appearance.headerTitleAlignment = .center //헤더 정렬
        calendar.headerHeight = 50 //헤더 높이

 

기본헤더를 사용하면 디자인처럼 헤더와 캘린더 사이의 간격을 디테일하게 조정하기에 어려움이 있고,

이전달/다음달을 넘기는 기능을 커스텀하기에 어려움이 있어

헤더와 이전 달/다음달 버튼을 커스텀했습니다.

 

3. 헤더 - 커스텀

- 기본 헤더 없애기

calendar.headerHeight = 0

- 레이아웃

//이전 달 버튼
	private var prevMonthButton = UIButton().then{ view in
        view.setBackgroundImage(Asset.icArrow2Left.image, for: .normal)
        view.snp.makeConstraints{ make in
            make.width.equalTo(7)
            make.height.equalTo(14)
        }
        view.contentMode = .scaleAspectFit
    }
    
 //다음 달 버튼
    private var nextMonthButton = UIButton().then{ view in
        view.setBackgroundImage(Asset.icArrow2Right.image, for: .normal)
        view.snp.makeConstraints{ make in
            make.width.equalTo(7)
            make.height.equalTo(14)
        }
    }
    
 // 가운데 년 월 헤더
    private var calendarHeaderTitle = UILabel().then { view in
        view.font = .pretendardMedium16
        view.textColor = .black
        view.text = DateUtil.shared.formattedString(for: Date(), format: DateFormat.yyyyMKR)
        //위 formattedString은 오늘 날짜(Date())를 년월일로 바꿔주는 함수로서, DateUtil로 따로 묶어두었습니다.
        
        /*
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy년 M월"

        return dateFormatter.string(from: Date())
        */
    }

- 이전/다음달 이동 버튼

    private var currentPage: Date?
    
    private func scrollCalendar(isPrev: Bool) { //클릭 시 이전 달 / 다음달 띄우기 위한 메소드
        let cal = Calendar.current
        var dateComponents = DateComponents()
        dateComponents.month = isPrev ? -1 : 1
            
        self.currentPage = cal.date(byAdding: dateComponents, to: self.currentPage ?? Date())
        self.calendar.setCurrentPage(self.currentPage!, animated: true)
                
        self.calendarHeaderTitle.text = DateUtil.shared.formattedString(for: self.currentPage!, format: DateFormat.yyyyMKR)
    }
    
       prevMonthButton.rx.tap //이전달 이동
            .subscribe({_ in
                self.scrollCurrentPage(isPrev: true)
            })
            .disposed(by: disposeBag)
        
      nextMonthButton.rx.tap //다음달 이동
            .subscribe({ _ in
                self.scrollCurrentPage(isPrev: false)
            })
            .disposed(by: disposeBag)

 

4. 캘린더 커스텀

        calendar.appearance.todayColor = .blue //오늘 날짜 배경색
        calendar.appearance.weekdayTextColor = .black //일~토 제목 타이틀 색
        calendar.weekdayHeight = 34 //일~토 제목 높이
        calendar.appearance.eventDefaultColor = .blue //이벤트 컬러
extension ViewController: FSCalendarDelegate, FSCalendarDataSource, FSCalendarDelegateAppearance { //달력
    // 날짜 글씨 색 지정
    func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, titleDefaultColorFor date: Date) -> UIColor? {
        let day = Calendar.current.component(.weekday, from: date) - 1
        
        if dateUtil.formattedString(for: dateUtil.now, format: .yyMMdd) != dateUtil.formattedString(for: date, format: .yyMMdd) { //'오늘'이 아닐 경우
            if Calendar.current.shortWeekdaySymbols[day] == "일" {
                return .red
            } else if Calendar.current.shortWeekdaySymbols[day] == "토" {
                return .blue
            } else {
                return .black
            }
        }
        else{ //오늘은 'white' 로 표시
            return .white
        }
    }
    
    //이벤트가 있는 날에 점을 몇개 표시할 것인지?
    func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int { //이벤트가 있을 시, 점을 몇개 표시할건지
    // 사전에 Date() 객체가 담길 datesWithEvent 배열을 선언해주었고, 이벤트가 있는 날짜들을 배열에 넣어두었습니다.
        if self.datesWithEvent.contains(date){ //만약 캘린더의 특정 날짜가 배열의 날짜를 포함한다면 표시 1개
            return 1
        }
        else{
            return 0
        }
    }
    
    //이벤트가 있는 날의 dot 표시 크기 조정
    func calendar(_ calendar: FSCalendar, willDisplay cell: FSCalendarCell, for date: Date, at monthPosition: FSCalendarMonthPosition) {
        let eventScaleFactor: CGFloat = 1.5
        cell.eventIndicator.transform = CGAffineTransform(scaleX: eventScaleFactor, y: eventScaleFactor)
    }
}

4번까지의 결과물은 다음과 같습니다.

 

5. 오늘 이전의 날짜 흐리게 만들기

extension ViewController: FSCalendarDelegate, FSCalendarDataSource, FSCalendarDelegateAppearance { //달력
    // 날짜 색 지정
    func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, titleDefaultColorFor date: Date) -> UIColor? {
        let day = Calendar.current.component(.weekday, from: date) - 1
        
        if dateUtil.formattedString(for: dateUtil.now, format: .yyMMdd) != dateUtil.formattedString(for: date, format: .yyMMdd) { //'오늘'이 아닐 경우
            if date < dateUtil.now { //이전 날짜일 경우 alpha값 추가
                if Calendar.current.shortWeekdaySymbols[day] == "일" {
                    return .salmon.withAlphaComponent(0.4)
                } else if Calendar.current.shortWeekdaySymbols[day] == "토" {
                    return .cornFlower.withAlphaComponent(0.4)
                } else {
                    return .black.withAlphaComponent(0.4)
                }
            }
            else if date > dateUtil.now{ //이후 날짜일 경우
                if Calendar.current.shortWeekdaySymbols[day] == "일" {
                    return .salmon
                } else if Calendar.current.shortWeekdaySymbols[day] == "토" {
                    return .cornFlower
                } else {
                    return .black
                }
            }
            else{
                return .black
            }
        }
        else{ //오늘일 경우 흰 글자
            return .white
        }
    }

 

반응형