๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŽ iOS

[Swift] FSCalendar ์ปค์Šคํ…€ ์บ˜๋ฆฐ๋” ์ •๋ฆฌ

by ํ‹ด๋”” 2023. 3. 16.
728x90
๋ฐ˜์‘ํ˜•

FSCalendar 

์ปค์Šคํ…€ ์บ˜๋ฆฐ๋”๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

 

GitHub - WenchaoD/FSCalendar: A fully customizable iOS calendar library, compatible with Objective-C and Swift

A fully customizable iOS calendar library, compatible with Objective-C and Swift - GitHub - WenchaoD/FSCalendar: A fully customizable iOS calendar library, compatible with Objective-C and Swift

github.com

์•Œ์•„๋ณด๊ณ  ์ •๋ฆฌํ•˜๋Š”๋ฐ ๊ฝค ์˜ค๋ž˜ ๊ฑธ๋ ธ๋Š”๋ฐ ๋Œ“๊ธ€ ํ•œ๋ฒˆ์”ฉ๋งŒ ๋‹ฌ์•„์ฃผ์‹œ๋ฉด ํž˜์ด ๋ ๊ฑฐ ๊ฐ™์Šต๋‹ˆ๋‹น ใ…Žใ…Ž 

Initializing

fileprivate var localCalendar = FSCalendar()

 

Delegate ์—ฐ๊ฒฐ

// FSCalendarDelegate
localCalendar.delegate = self 

// FSCalendarDataSource 
localCalendar.dataSource = self

// FSCalendarDelegateAppearance
  • FSCalendarDelegate, FSCalendarDataSource ๋Š” UICollectionViewDelegate์™€ UICollectionViewDataSource์™€ ์‚ฌ์šฉ๋ฒ•์ด ์œ ์‚ฌํ•จ(์‹ค์ œ๋กœ UICollectionView๋กœ ๋˜์–ด ์žˆ์Œ)
  • FSCalendarDelegateAppearance๋กœ ์ปค์Šคํ…€ ๊ฐค๋Ÿฌ๋ฆฌ์˜ ์™ธํ˜•์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Œ

 

layout update

	func calendar(_ calendar: FSCalendar, boundingRectWillChange bounds: CGRect, animated: Bool) {
        self.view.layoutIfNeeded()
    }
  • ์ž๋™์œผ๋กœ ๋ ˆ์ด์•„์›ƒ์ด ์—…๋ฐ์ดํŠธ ๋˜์ง€ ์•Š์„ ๋•Œ boundingRectWillChange ํ˜ธ์ถœํ•ด์„œ ์—…๋ฐ์ดํŠธ

 

Calendar ์ „์ฒด

  • ์˜ค๋Š˜ ์„ ํƒ ํ•ด์ง€ํ•˜๊ธฐ
    • default๋กœ ์˜ค๋Š˜์ด ์„ ํƒ๋˜์–ด ์žˆ์Œ
localCalendar.today = nil

 

  • ์Šคํฌ๋กค ๊ธฐ๋Šฅ ๋ง‰๊ธฐ
    • default๋กœ ๋‚ ์งœ ๋ถ€๋ถ„์„ ์Šคํฌ๋กค ํ•˜๋ฉด ๋‹ค์Œ์ด๋‚˜ ์ด์ „ ์›”๋กœ ์ด๋™๋จ
localCalendar.scrollEnabled = false

 

  • UI ์—…๋ฐ์ดํŠธ
localCalendar.layoutIfNeeded()

 

  • ๋‚˜๋ผ (ํ•œ๊ตญ์œผ๋กœ) ์„ค์ •ํ•˜๊ธฐ
localCalendar.locale = Locale.init(identifier: "ko_KR")
// or
localCalendar.locale = Locale(identifier: "ko_KR")

 

  • ์ด์ „ ํ˜น์€ ์ดํ›„ ๋‚ ์งœ ์•ˆ๋œจ๋„๋ก ์ˆ˜์ •
    • ๊ธฐ๋ณธ์œผ๋กœ ์ด์ „, ์ดํ›„ ๋‚ ์งœ๊ฐ€ ํ˜„์žฌ ๋‹ฌ๋ ฅ์— ๋œจ๊ฒŒ ๋˜์–ด ์žˆ์Œ

localCalendar.placeholderType = .none

 

  • ์„ ํƒ๋œ ๋‚ ์งœ ํ‘œ์‹œ๋ฅผ ๋™๊ทธ๋ผ๋ฏธ์—์„œ ๋„ค๋ชจ๋กœ ๋ณ€๊ฒฝํ•˜๊ธฐ
    • ๋‚ ์งœ๋ฅผ ์„ ํƒํ•˜๋ฉด ๋‚˜์˜ค๋Š” ๋™๊ทธ๋ผ๋ฏธ๋Š” FSCalendarCell์— layer๋กœ ๋“ค์–ด๊ฐ€ ์žˆ์œผ๋ฉฐ ๋‚ ์งœ๊ฐ€ ํ‘œ์‹œ๋˜๋Š” titleLabel ๋ณด๋‹ค ์ž‘์€ ์˜์—ญ์œผ๋กœ ๊ทธ๋ ค์ง
    • layer์˜ ํฌ๊ธฐ๋ฅผ ํ‚ค์šฐ๋”๋ผ๋„ ์…€ ์‚ฌ์ด์— ์—ฌ๋ฐฑ์ด ์žˆ์œผ๋ฏ€๋กœ ์ฃผ์˜ ๊ทธ ๋•Œ๋Š” inserBy๋กœ inset์„ ํ‚ค์›Œ ์คŒ
localCalendar.appearance.borderRadius = 0.0

Header Title

  • ๋‹ฌ๋ ฅ์—์„œ ์š”์ผ ์œ„์— ๋‚ ์งœ๊ฐ€ ํ‘œ์‹œ๋˜๋Š” ์˜์—ญ์ด header๋กœ ๋ถˆ๋ฆผ
  • ์•„์ง header title์„ ๋งจ ์œ„์— ๋ถ™์ด๋Š” ๋ฐฉ๋ฒ•์„ ๋ชจ๋ฅด๊ฒ ์Œ ์ด ์‚ฌ์ง„์€ snapkit์œผ๋กœ ๋‹ค๋ฅธ ๋ทฐ๋ž‘ ์—ฎ์–ด์„œ ์œ„์— ์ž˜๋ฆฌ๋„๋ก ๋งŒ๋“  ๊ฒƒ

 

  • ํ—ค๋” ๋†’์ด 
localCalendar.headerHeight = 48

 

  • ํ—ค๋” ํƒ€์ดํ‹€ ๋‚ ์งœ ๋ฐ์ดํ„ฐ ํฌ๋ฉง ์ง€์ •
localCalendar.appearance.headerDateFormat = "YYYY๋…„ MM์›”"

 

  • ํ—ค๋” ํƒ€์ดํ‹€ ์ƒ‰์ƒ
localCalendar.appearance.headerTitleColor = .red // UIColor

 

  • ํ—ค๋” ํƒ€์ดํ‹€ ํฐ๋“œ
localCalendar.appearance.headerTitleFont = UIFont(name: UIFont.NormalFont.medium, size: 18)!

 

  • ํ—ค๋” ํƒ€์ดํ‹€ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ œ๊ฑฐ
    • default๋กœ ๋‹ฌ๋ ฅ์ด ์ด์ „์ด๋‚˜ ๋‹ค์Œ ๋‹ฌ๋กœ ๋„˜์–ด๊ฐ€๋ฉด ํ—ค๋” ํƒ€์ดํ‹€์—์„œ ์ด์ „ ๋‹ฌ๋ ฅ์˜ ํ—ค๋” ํƒ€์ดํ‹€์˜ ์•ŒํŒŒ๊ฐ’์ด ์กฐ์ •๋˜๋ฉด์„œ ์‚ฌ๋ผ์ง€๊ฑฐ๋‚˜ ๋‚˜ํƒ€๋‚˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์žˆ์Œ
localCalendar.appearance.headerMinimumDissolvedAlpha = 0.0

 


Weekday(์š”์ผ)

  • ์š”์ผ ์˜์—ญ ๋†’์ด ์„ค์ •
localCalendar.weekdayHeight = 46

 

  • ์š”์ผ ์˜์—ญ ํ…์ŠคํŠธ ์ปฌ๋Ÿฌ
localCalendar.appearance.weekdayTextColor = .bentleyColor(.black70)

 

  • ์š”์ผ ์˜์—ญ ํฐํŠธ
localCalendar.appearance.weekdayFont = UIFont(name: UIFont.NormalFont.medium, size: 14)!

Day

  • ๋‚ ์งœ ํฐํŠธ ๋ณ€๊ฒฝ
localCalendar.appearance.titleFont = UIFont(name: UIFont.NormalFont.medium, size: 14)!

FSCalendarDataSource

  • ์ตœ์†Œ ๋‚ ์งœ ์ง€์ •
    • ์˜ˆ์‹œ ์ฝ”๋“œ๋Š” ์˜ค๋Š˜ ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋„๋ก ์ง€์ •ํ•˜๋Š” ๊ฒƒ
    func minimumDate(for calendar: FSCalendar) -> Date {
        return Date()
    }

 

  • ์ตœ๋Œ€ ๋‚ ์งœ ์ง€์ •
    • ์˜ˆ์‹œ ์ฝ”๋“œ๋Š” ์ด๋ฒˆ๋‹ฌ ํฌํ•จ 3๊ฐœ์›” ๊นŒ์ง€๋งŒ ๋‚˜์˜ค๋„๋ก ์ง€์ •
	func maximumDate(for calendar: FSCalendar) -> Date {
        let currentCaledar = Calendar(identifier: .gregorian)
        let startComponents = currentCaledar.dateComponents([.year, .month], from: Date())
        let startOfMonth = currentCaledar.date(from: startComponents)!
        
        var endComponents = DateComponents()
        let limitDate = Singleton.sharedObject.initData?.reservationLimitDate
        endComponents.month = Int(limitDate ?? "3") ?? 3
        endComponents.day = -1
        return Calendar(identifier: .gregorian).date(byAdding: endComponents, to: startOfMonth)!

    }

 


FSCalendarDelegate

  • layout update
    • FSCalendar๋Š” ์ž๋™์œผ๋กœ ํฌ๊ธฐ๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— AutoLayout, frame์œผ๋กœ ์„ค์ •ํ•œ ๋’ค ์—…๋ฐ์ดํŠธ ํ•ด์ค˜์•ผ ํ•จ (ํ…Œ์ŠคํŠธ ํ•ด๋ณธ ๊ฒฐ๊ณผ ์žˆ๋“  ์—†๋“  ์ฐจ์ด ์—†์Œ)
	func calendar(_ calendar: FSCalendar, boundingRectWillChange bounds: CGRect, animated: Bool) {
        self.view.layoutIfNeeded()
    }

 

  • ๋‚ ์งœ ์„ ํƒ didSelect delegate
    • ๋‚ ์งœ ์„ ํƒ ํ›„ ์ฒ˜๋ฆฌํ•  ์ด๋ฒคํŠธ๋ฅผ ์ง€์ •ํ•ด ์ค„ ์ˆ˜ ์žˆ์Œ. ์„ ํƒ๋œ ๋‚ ์งœ๋Š” date ๋ณ€์ˆ˜์— ๋‹ด๊ฒจ์„œ ๋‚˜์˜ด
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
    print(date)
}

 


FSCalendarAppearance

  • ํŠน์ • ์กฐ๊ฑด์ผ ๋•Œ ๋‚ ์งœ ์ƒ‰์ƒ ๋ณ€๊ฒฝ
    • ์˜ˆ์‹œ ์ฝ”๋“œ์—๋Š” holiday ๋ผ๋Š” ์กฐ๊ฑด์— ๋”ฐ๋ผ ํ•ด๋‹น ๋‚ ์งœ์˜ ์ƒ‰์ƒ์„ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ
func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, titleDefaultColorFor date: Date) -> UIColor? {
	if holiday {
		return .red
	} else {
		return .black
	}
}

CustomCell

  • ํ˜„์žฌ ๋‹ฌ๋ ฅ์— ์žˆ๋Š” cell ๊ณผ date, position ์–ป์–ด์˜ค๊ธฐ
localCalendar.visibleCells().forEach { (cell) in
    let date = self.localCalendar.date(for: cell)
    let position = self.localCalendar.monthPosition(for: cell)
		// custom configuration
    // self.configureCell(cell, for: date, at: position) 
}

 

  • FSCalendarDataSource ์—์„œ ์ปค์Šคํ…€ ์…€๊ณผ identifier ์ง€์ •
    func calendar(_ calendar: FSCalendar, cellFor date: Date, at position: FSCalendarMonthPosition) -> FSCalendarCell {
        let cell = calendar.dequeueReusableCell(withIdentifier: "cell", for: date, at: position)
        return cell
    }

 

  • register cell
localCalendar.register(CalendarCollectionViewCell.self, forCellReuseIdentifier: "cell")

 

  • ์บ˜๋ฆฐ๋” display ๋ ๋•Œ ์ตœ์ดˆ ์„ค์ •
    • ์ปค์Šคํ…€ ์…€์ด ๋กœ๋“œ ๋ ๋•Œ ์ƒํƒœ๋ฅผ ์ง€์ •ํ•˜์—ฌ UI ๋ณ€๊ฒฝ๋„ ๊ฐ€๋Šฅ
    func calendar(_ calendar: FSCalendar, willDisplay cell: FSCalendarCell, for date: Date, at monthPosition: FSCalendarMonthPosition) {
        // custom configuration
        // configureCell(cell, for: date, at: monthPosition)
    }

 

  • configure cell
    • ์˜ˆ์‹œ ์ฝ”๋“œ
func configureCell(_ cell: FSCalendarCell?, for date: Date?, at position: FSCalendarMonthPosition) {
        guard let date = date else {
            return
        }
        
        dateFormatter.dateFormat = "yyyy-MM-dd"
        
        for availableDate in availableDateList {
            let dateString = dateFormatter.string(from: date)
            if dateString == availableDate.day {
                if availableDate.holiday ?? true {
                    cell?.isUserInteractionEnabled = false
                }
            }
        }
        
        let selectedDateString = dateFormatter.string(from: date)
        let todayDateString = dateFormatter.string(from: Date())
        
        let calendarCell = cell as! CalendarCollectionViewCell
        
        if selectedDateString == todayDateString {
            calendarCell.selectionType = localCalendar.selectedDate == date ? .todaySelect : .todayDiselect
        } else {
            calendarCell.selectionType = localCalendar.selectedDate == date ? .select : .normal
        }
    }

 

  • custom cell ์ƒ์„ฑ
class CalendarCollectionViewCell: FSCalendarCell {
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init!(coder aDecoder: NSCoder!) {
        fatalError("init(coder:) has not been implemented")
    }
    
     override func layoutSubviews() {
        super.layoutSubviews()
     }
     
     override func configureAppearance() {
        super.configureAppearance()
     }
}
728x90
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€