I have implemented two calendars in one viewcontroller:
month view - implemented with storyboard week view - implemented with storybard, but cell in code. and I am having problem with the week view version. its cell(collectionviewcell - named 'EventListCell') is programmatically made, and I registered the cell to the collection view (named 'eventListMonthView') .
I had to give different configuration and delegate, datasource for the two calendars, so I used IF to make different settings.
I had constraints on labels in the problem making cell(EventListCell) it has two labels and it has leading and top constraints.
but those constraints only get updated for visible cells at first when the view is loaded, and not for other cells when I scroll to see other cells.
I believe willDisplay function has to do with this, but I don't see anything wrong I've done here... need a help here :(
my code:
//
// SelectDateCandidateViewController.swift
// teampang
//
// Created by 선민승 on 2021/10/13.
//
import UIKit
import JTAppleCalendar
import RxSwift
import RxCocoa
class SelectDateCandidateViewController: UIViewController, UIGestureRecognizerDelegate {
let selectDateViewModel = SelectDateViewModel()
var disposeBag = DisposeBag()
private let formatter = DateFormatter()
private var firstDate: Date?
private var planBoundary: [String] = []
private let days = ["월", "화", "수", "목", "금", "토", "일"]
private let calendar = Calendar.current
var currentlySelctedDate = Date()
@IBOutlet var timeSettingUIView: UIView!
@IBOutlet var timeSettingLabel: UILabel!
@IBOutlet var timeSettingUIViewHeight: NSLayoutConstraint! //처음엔 0 -> 48
@IBOutlet var eventListMonthView: JTACMonthView!
@IBOutlet var eventListContainerView: UIView!
@IBOutlet var JTACMonthView: JTACMonthView!
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = false
self.navigationController?.navigationBar.barTintColor = .white
self.navigationController?.navigationBar.clipsToBounds = true
}
override func viewDidLoad() {
super.viewDidLoad()
bindUI()
setNavigation()
setIBOutlets()
setCalendar()
}
@objc private func popToPrevious() {
// our custom stuff
self.navigationController?.popViewController(animated: true)
}
private func bindUI() {
selectDateViewModel.input.selectedDates
.subscribe(onNext:{ list in
UIView.animate(withDuration: 0.5,
delay: 0.0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.5,
options: [],
animations: {
self.timeSettingUIViewHeight.constant = list.count == 0 ? 0 : 48
self.timeSettingLabel.isHidden = list.count == 0 ? true : false
self.timeSettingLabel.alpha = list.count == 0 ? 0 : 1
guard let date = self.JTACMonthView.visibleDates().monthDates.first?.date else { return }
self.timeSettingUIView.layoutIfNeeded()
self.view.layoutIfNeeded()
self.JTACMonthView.reloadData(withAnchor: date, completionHandler: nil)
})
})
.disposed(by: disposeBag)
}
private func setCalendar() {
JTACMonthView.scrollToDate(Date(), animateScroll: false) {
self.JTACMonthView.selectDates([Date()])
}
JTACMonthView.cellSize = 45/812*view.bounds.height
JTACMonthView.allowsMultipleSelection = true
JTACMonthView.scrollingMode = .none
eventListMonthView.scrollToDate(Date(), animateScroll: false) {
self.eventListMonthView.selectDates([Date()])
}
eventListMonthView.minimumInteritemSpacing = 0
eventListMonthView.minimumLineSpacing = 0
eventListMonthView.cellSize = 171/812*view.bounds.height
eventListMonthView.scrollDirection = .horizontal
eventListMonthView.scrollingMode = .none
}
private func setIBOutlets() {
timeSettingUIView.backgroundColor = .grey200
timeSettingLabel.setLabel(text: "시간 설정", font: .spoqaMedium(size: 15), color: .darkGrey, kernValue: -0.6)
eventListContainerView.backgroundColor = .grey100
eventListContainerView.layer.masksToBounds = false
eventListContainerView.layer.shadowOffset = CGSize(width: 0,height: -4)
eventListContainerView.layer.shadowRadius = 10
eventListContainerView.layer.shadowOpacity = 1
eventListContainerView.layer.shadowColor = UIColor(red: 0.024, green: 0.247, blue: 0.345, alpha: 0.1).cgColor
// let flowLayout = UICollectionViewFlowLayout()
// flowLayout.collectionView?.frame = CGRect(x: 0, y: 0, width: 171, height: 171)
// flowLayout.scrollDirection = .horizontal
eventListMonthView.register(EventListCell.self, forCellWithReuseIdentifier: EventListCell.identifier)
eventListMonthView.backgroundColor = .clear
// eventListMonthView.collectionViewLayout = flowLayout
// eventListMonthView.showsHorizontalScrollIndicator = false
}
private func setNavigation() {
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
self.navigationController?.navigationBar.backgroundColor = .clear
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: UIFont.spoqaMedium(size: 16),NSAttributedString.Key.foregroundColor: UIColor.darkGrey, NSAttributedString.Key.kern: -0.64]
title = "날짜 범위 선택"
navigationItem.leftBarButtonItem = UIBarButtonItem (
image: UIImage(named: "Glyph")?.withRenderingMode(.alwaysOriginal),
style: .plain,
target: self,
action: #selector(popToPrevious)
)
navigationItem.rightBarButtonItem = UIBarButtonItem (
image: UIImage(named: "check_checked")?.withRenderingMode(.alwaysOriginal),
style: .plain,
target: self,
action: #selector(popToPrevious)
)
}
}
// MARK: Calendar ConfigureCell
extension SelectDateCandidateViewController {
private func configureCell(_ calendar: JTACMonthView, view: JTACDayCell?, cellState: CellState) {
if calendar == JTACMonthView {
guard let cell = view as? DateCell else { return }
cell.selectedView.backgroundColor = .primaryColor
// 달력 기본 날짜 설정
cell.dateLabel.text = cellState.text
cell.dateLabel.font = .poppinsRegular(size: 12)
cell.dateLabel.textColor = .grey600
// 선택되면 selectedView 칠해지게 처리
handleCellSelected(cell: cell, cellState: cellState)
handleCellVisibility(cell: cell, cellState: cellState)
// planStartDate~endDate 범위 속 날짜만 black 컬러 처리
handleCellTextColor(calendar, cell: cell, cellState: cellState)
} else {
print(cellState)
guard let cell = view as? EventListCell else { return }
let formatter = DateFormatter() // Declare this outside, to avoid instancing this heavy class multiple times.
formatter.dateFormat = "M월 d일"
let time = formatter.string(from: cellState.date)
cell.monthDateLabel.text = time
var day = "월요일"
switch cellState.day {
case .sunday:
day = "일요일"
case .monday:
day = "월요일"
case .tuesday:
day = "화요일"
case .wednesday:
day = "수요일"
case .thursday:
day = "목요일"
case .friday:
day = "금요일"
case .saturday:
day = "토요일"
}
cell.dayLabel.text = day
handleEventListCellVisibility(cell: cell, cellState: cellState)
}
}
func handleCellTextColor(_ calendar: JTACMonthView, cell: DateCell, cellState: CellState) {
if cellState.isSelected {
cell.dateLabel.textColor = .white
}
}
// 선택되면 selectedView 칠해지게 처리
private func handleCellSelected(cell: DateCell, cellState: CellState) {
cell.selectedView.isHidden = !cellState.isSelected
cell.selectedView.setRounded(radius: nil)
cell.selectedView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]
}
private func handleCellVisibility(cell: DateCell, cellState: CellState){
cell.isHidden = cellState.dateBelongsTo == .thisMonth ? false : true
}
private func handleEventListCellVisibility(cell: EventListCell, cellState: CellState){
cell.isHidden = cellState.dateBelongsTo == .thisMonth ? false : true
}
}
extension SelectDateCandidateViewController: JTACMonthViewDataSource {
func configureCalendar(_ calendar: JTACMonthView) -> ConfigurationParameters {
formatter.dateFormat = "yyyy MM dd"
formatter.timeZone = Calendar.current.timeZone
formatter.locale = Calendar.current.locale
let startDate = formatter.date(from: "1999 01 01")!
let endDate = formatter.date(from: "3000 02 01")!
if calendar == JTACMonthView {
return ConfigurationParameters(startDate: startDate, endDate: endDate)
} else {
print("calendar datasource: \(calendar)")
return ConfigurationParameters(startDate: startDate,
endDate: endDate,
numberOfRows: 1,
generateInDates: .forFirstMonthOnly,
generateOutDates: .off,
hasStrictBoundaries: false)
}
}
}
extension SelectDateCandidateViewController: JTACMonthViewDelegate {
func calendar(_ calendar: JTACMonthView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTACDayCell {
if calendar == JTACMonthView {
let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "dateCell", for: indexPath) as! DateCell
self.calendar(calendar, willDisplay: cell, forItemAt: date, cellState: cellState, indexPath: indexPath)
return cell
} else {
let cell = calendar.dequeueReusableCell(withReuseIdentifier: "dateCell", for: indexPath) as! EventListCell
self.calendar(calendar, willDisplay: cell, forItemAt: date, cellState: cellState, indexPath: indexPath)
return cell
}
}
func calendar(_ calendar: JTACMonthView, willDisplay cell: JTACDayCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) {
if calendar == JTACMonthView {
configureCell(calendar, view: cell, cellState: cellState)
} else {
print(cell)
configureCell(calendar, view: cell, cellState: cellState)
}
}
func calendar(_ calendar: JTACMonthView, didSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {
if calendar == JTACMonthView {
guard let validCell = cell as? DateCell else { return }
validCell.selectedView.isHidden = false
//서버 전달용 포맷
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ko_KR")
formatter.dateFormat = "YYYY-MM-dd"
//라벨 용 포맷
let labelFormatter = DateFormatter()
labelFormatter.locale = Locale(identifier: "ko_KR")
labelFormatter.dateFormat = "YYYY년 M월 d일"
//서버에 전달 될 배열 초기화
// planBoundary.removeAll()
//한번만 눌렀을 때에도 배열에 selectedDate 데이터 들어감
planBoundary.insert(formatter.string(from: calendar.selectedDates.first!), at: 0)
selectDateViewModel.input.selectedDates.onNext(calendar.selectedDates)
//한번만 누른 값 아래 라벨 내용에 업데이트
let selected = calendar.selectedDates.first!
// eventCreateViewModel.input.startDate.accept(selected)
firstDate = date
currentlySelctedDate = date
configureCell(calendar,view: cell, cellState: cellState)
}
}
func calendar(_ calendar: JTACMonthView, didDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {
if calendar == JTACMonthView {
firstDate = nil
currentlySelctedDate = date
selectDateViewModel.input.selectedDates.onNext(calendar.selectedDates)
configureCell(calendar,view: cell, cellState: cellState)
}
}
func calendar(_ calendar: JTACMonthView, shouldSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) -> Bool {
if calendar == JTACMonthView {
return true
} else {
return false
}
}
func calendar(_ calendar: JTACMonthView, shouldDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) -> Bool {
if calendar == JTACMonthView {
return true
} else {
return false
}
}
func calendar(_ calendar: JTACMonthView, headerViewForDateRange range: (start: Date, end: Date), at indexPath: IndexPath) -> JTACMonthReusableView {
if calendar == JTACMonthView {
let formatter = DateFormatter() // Declare this outside, to avoid instancing this heavy class multiple times.
formatter.dateFormat = "yyyy년 M월"
let header = calendar.dequeueReusableJTAppleSupplementaryView(withReuseIdentifier: "DateHeader", for: indexPath) as! DateHeader
header.monthTitle.text = formatter.string(from: range.start)
header.monthTitle.font = .poppinsRegular(size: 14)
header.monthTitle.textColor = .grey600
let days = [header.monday, header.saturday, header.friday, header.thursday, header.wednesday, header.tuesday, header.sunday]
days.forEach {
$0?.font = .poppinsLight(size: 12)
$0?.textColor = .grey400
$0?.textAlignment = .center
}
header.sunday.textColor = UIColor(red: 0.992, green: 0.196, blue: 0.349, alpha: 0.6)
header.saturday.textColor = UIColor(red: 0.992, green: 0.196, blue: 0.349, alpha: 0.6)
return header
} else {
return UICollectionReusableView() as! JTACMonthReusableView
}
}
func calendarSizeForMonths(_ calendar: JTACMonthView?) -> MonthSize? {
if calendar == JTACMonthView {
return MonthSize(defaultSize: 60)
} else {
return nil
}
}
}
extension SelectDateCandidateViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = 171
let height = 164
return CGSize(width: width, height: height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 20)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
for more information : https://github.com/patchthecode/JTAppleCalendar/issues/1341