JTAppleCalendar - having issues with cells' autolayout updating only for visible cells

Viewed 192

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 :(

enter image description 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

1 Answers

Remove Calendar.cellsize from the code.

Calendar.minimumInteritemSpacing = 0, Calendar.minimumLineSpacing = 0, will adjust the calendar cell size automatically when scrolling between the months.

Related