patchthecode / JTAppleCalendar

The Unofficial Apple iOS Swift Calendar View. Swift calendar Library. iOS calendar Control. 100% Customizable
https://patchthecode.com
MIT License
7.57k stars 807 forks source link

Calendar components are not aligned properly #1069

Closed joseph-francis closed 5 years ago

joseph-francis commented 5 years ago

(Required) Version Number: V.7.1.7

Description

The calendar works fine when viewed initially. The calendar messes up when scrolled horizontally. I'm using IGListKit.

I have attached a video with this. As you can see, the circle view on the dates messes up once I scroll horizontally.

Steps To Reproduce

JTAppleCalendarDataSource and JTAppleCalendarDelegate

func configureCalendar(_ calendar: JTAppleCalendarView) -> ConfigurationParameters {
        if isCalendarExpanded {
            return ConfigurationParameters(startDate: currentDate.start(of: .month), endDate: goal?.endDate ?? currentDate.endOfWeek, numberOfRows: 6, generateInDates: .forAllMonths, generateOutDates: .tillEndOfGrid, firstDayOfWeek: .sunday, hasStrictBoundaries: false)
        } else {
            calendarView.scrollToDate(currentDate.startOfWeek, triggerScrollToDateDelegate: false, animateScroll: false, extraAddedOffset: CGFloat(0), completionHandler: nil)
            return ConfigurationParameters(startDate: goal?.startDate.startOfWeek ?? currentDate.startOfWeek, endDate: currentDate.endOfWeek, numberOfRows: 1, calendar: Calendar.current, generateInDates: .forFirstMonthOnly, generateOutDates: .off, firstDayOfWeek: .sunday, hasStrictBoundaries: true)
        }
    }

    func calendar(_ calendar: JTAppleCalendarView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTAppleCell {
        guard calendar == self.calendarView, let cell = self.calendarView.dequeueReusableJTAppleCell(withReuseIdentifier: cellID, for: indexPath) as? CalendarCell else { return JTAppleCell() }
        configure(cell: cell, cellState: cellState, formatter: formatter, currentDate: currentDate)
        cell.dateLbl.text = cellState.text
        return cell
    }

JTAppleCalendar instance setup:

fileprivate func setupViews() {
        calendarView.register(CalendarCell.self, forCellWithReuseIdentifier: cellID)
        calendarView.isPagingEnabled = true
        calendarView.allowsMultipleSelection = false
        calendarView.isRangeSelectionUsed = false
        calendarView.selectDates([currentDate], triggerSelectionDelegate: false)
        calendarView.cellSize = (bounds.size.width - calendarViewLeadingConstraint.constant - calendarViewTrailingConstraint.constant)/7
        calendarView.scrollDirection = .horizontal
        calendarView.isScrollEnabled = true
        calendarView.alwaysBounceVertical = false
        calendarView.minimumLineSpacing = 0
        calendarView.minimumInteritemSpacing = 0

        calendarView.calendarDataSource = self
        calendarView.calendarDelegate = self
    }

The transition of week view to month view happens when a cell is selected. A delegate is used to inform IGListKit's SectionViewController to reload the JTAppleCalendar's instance.

func calendar(_ calendar: JTAppleCalendarView, didSelectDate date: Date, cell: JTAppleCell?, cellState: CellState) {
    handleDelegate()
}

IGListKit's SectionViewController's code related to calendarView's reloading. The delegate from the cell calls a function which then calls handleCalendarExpansion(cell: DetailsSetGoalCalendarCell)

fileprivate func handleCalendarExpansion(cell: DetailsSetGoalCalendarCell) {

        isCalendarExpanded = !isCalendarExpanded
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.6, options: [], animations: {
            self.collectionContext?.invalidateLayout(for: self)
        }) { (true) in
            cell.isCalendarExpanded = self.isCalendarExpanded
            cell.layoutIfNeeded()
            cell.calendarView.reloadData()
        }
    }

Expected Behavior

The expected behavior is for the calendar cells to not mess up the circle views on it and display proper circle views according to the code. The styling works as expected when viewed initially. The calendar cells start to mess up when scrolling is performed.

Additional Context

This is the configure(cell: CalendarCell, cellState: CellState, formatter: DateFormatter, currentDate: Date) function in the cellForItemAt(...)

func configure(cell: CalendarCell, cellState: CellState, formatter: DateFormatter, currentDate: Date) {

        let cellDateStr = formatter.string(from: cellState.date)
        let currentDateStr = formatter.string(from: currentDate)

        formatter.dateFormat = "yyyy MM dd"
        let allDatesInStr = dates.map({formatter.string(from: $0)})

        var allDates = [Date]()
        for dateStr in allDatesInStr {
            guard let date = formatter.date(from: dateStr) else { continue }
            allDates.append(date)
        }

        guard let currentDate = formatter.date(from: currentDateStr), let cellDate = formatter.date(from: cellDateStr), let startDate = dates.min(), let endDate = dates.max() else { return } //only need y,m,d

        if currentDate.interval(ofComponent: .day, fromDate: endDate.start(of: .day)) >= 1 {
            //SetGoal is done at this point
            let style: CalendarCellStyles = cellDate == currentDate ? .goalComplete(.yes) : .goalComplete(.no)
            attributes(forCell: cell, withStyle: style)

        } else if cellDateStr == currentDateStr {

            if cellDateStr == formatter.string(from: startDate) {
                attributes(forCell: cell, withStyle: .startDateIsToday)
            } else if cellDateStr == formatter.string(from: endDate) {
                attributes(forCell: cell, withStyle: .endDateIsToday)
            } else {
                attributes(forCell: cell, withStyle: .today)
            }

        } else if cellState.dateBelongsTo == .previousMonthWithinBoundary || cellState.dateBelongsTo == .followingMonthWithinBoundary || cellState.dateBelongsTo == .previousMonthOutsideBoundary || cellState.dateBelongsTo == .followingMonthOutsideBoundary {
            cell.alpha = 0

        } else if cellDateStr == formatter.string(from: startDate) {
            attributes(forCell: cell, withStyle: .startDate)

        } else if cellDateStr == formatter.string(from: endDate) {
            attributes(forCell: cell, withStyle: .endDate)

        } else if (cellDate < currentDate) && (startDate...endDate).contains(cellDate) {

            if allDates.contains(cellDate) {
                attributes(forCell: cell, withStyle: .readInPast)
            } else {
                attributes(forCell: cell, withStyle: .didNotReadInPast)
            }

        } else if (cellDate > currentDate) && (startDate...endDate).contains(cellDate) {
            attributes(forCell: cell, withStyle: .needToReadInFuture)

        } else {
            attributes(forCell: cell, withStyle: .regular)

        }
    }

The attributes(forCell: cell, withStyle: .something) function:

fileprivate func attributes(forCell cell: CalendarCell, withStyle style: CalendarCellStyles) {

        cell.alpha = 1

        switch style {
        case .regular:
            cell.dateLbl.textColor = dateLblColor()
            cell.selectedView.backgroundColor = nil
            cell.selectedView.layer.borderWidth = 0.0
            cell.selectedView.isHidden = true
            cell.imageView.isHidden = true

        case .readInPast:
            cell.dateLbl.textColor = UIColor.black.withAlphaComponent(0.34)
            cell.selectedView.backgroundColor = greyishBackgroundColor()
            cell.selectedView.layer.borderWidth = 0.0
            cell.selectedView.isHidden = false
            cell.imageView.isHidden = true

        case .today:
            cell.dateLbl.textColor = UIColor.white
            cell.selectedView.backgroundColor = appColor
            cell.selectedView.layer.borderWidth = 0.0
            cell.selectedView.isHidden = false
            cell.imageView.isHidden = true

        case .startDate:
            cell.dateLbl.textColor = UIColor.white
            cell.selectedView.backgroundColor = UIColor.black
            cell.selectedView.layer.borderWidth = 0.0
            cell.selectedView.isHidden = false
            cell.imageView.isHidden = false
            cell.imageView.image = #imageLiteral(resourceName: "rocket")
            cell.imageView.tintColor = UIColor.black

        case .didNotReadInPast:
            let greish = greyishBackgroundColor()
            cell.dateLbl.textColor = greish
            cell.selectedView.layer.borderColor = greish.cgColor
            cell.selectedView.layer.borderWidth = 1.0
            cell.selectedView.backgroundColor = nil
            cell.selectedView.isHidden = false
            cell.imageView.isHidden = true

        case .needToReadInFuture:
            cell.dateLbl.textColor = appColor
            cell.selectedView.layer.borderColor = appColor.cgColor
            cell.selectedView.layer.borderWidth = 1.0
            cell.selectedView.backgroundColor = nil
            cell.selectedView.isHidden = false
            cell.imageView.isHidden = true

        case .endDate:
            cell.dateLbl.textColor = UIColor.white
            cell.selectedView.backgroundColor = UIColor.black
            cell.selectedView.layer.borderWidth = 0.0
            cell.selectedView.isHidden = false
            cell.imageView.isHidden = false
            cell.imageView.image = #imageLiteral(resourceName: "setGoalFlag")
            cell.imageView.tintColor = UIColor.black

        case .startDateIsToday:
            cell.dateLbl.textColor = UIColor.white
            cell.selectedView.backgroundColor = appColor
            cell.selectedView.isHidden = false
            cell.selectedView.layer.borderWidth = 0.0
            cell.imageView.isHidden = false
            cell.imageView.image = #imageLiteral(resourceName: "rocket")
            cell.imageView.tintColor = appColor

        case .endDateIsToday:
            cell.dateLbl.textColor = UIColor.white
            cell.selectedView.backgroundColor = appColor
            cell.selectedView.isHidden = false
            cell.selectedView.layer.borderWidth = 0.0
            cell.imageView.isHidden = false
            cell.imageView.image = #imageLiteral(resourceName: "setGoalFlag")
            cell.imageView.tintColor = appColor

        case .goalComplete(.yes):
            cell.dateLbl.textColor = appColor
            cell.selectedView.backgroundColor = appColor
            cell.selectedView.isHidden = false
            let cellWidth = cell.bounds.size.width
            let fraction: CGFloat = 6.5/10
            cell.selectedView.frame.size = CGSize.init(width: cellWidth*fraction, height: cellWidth * fraction)
            cell.selectedView.layer.cornerRadius = (cellWidth * fraction)/2
            cell.selectedView.layer.borderWidth = 0.0
            cell.imageView.isHidden = true

        case .goalComplete(.no):
            let greish = greyishBackgroundColor()
            cell.dateLbl.textColor = greish
            cell.selectedView.backgroundColor = greish
            cell.selectedView.isHidden = false
            let cellWidth = cell.bounds.size.width
            let fraction: CGFloat = 6.5/10
            cell.selectedView.frame.size = CGSize.init(width: cellWidth*fraction, height: cellWidth*fraction)
            cell.selectedView.layer.cornerRadius = (cellWidth*fraction)/2
            cell.selectedView.layer.borderWidth = 0.0
            cell.imageView.isHidden = true
        }
    }

My intention is to have a week view calendar and a month view calendar. When clicked on the week view calendar, I want it to expand to a month view calendar with the attributes I want to have on each dates with consistancy.

I apologize for this long issue. I tried several things for 2 weeks, but I still couldn't figure it out. I will really appreciate if you could suggest or guide me on fixing this problem.

bivant commented 5 years ago

I have attached a video with this. As you can see, the circle view on the dates messes up once I scroll horizontally.

I think it will be clearer if you attach screenshots of "correct" and "messed up" states as it is not obvious what is wrong with all those moves on the screen. Also it might help if you check the behavior on the master branch as there is no sample project from you and a lot of fixes have been applied by the author of the calendar.

patchthecode commented 5 years ago

I'm trying to make your long issue simpler so that i can identify what the problem is. Let us deal with your week view calendar first ok? (since the problem seems to be only with how your cells are displayed)

  1. On your weekview calendar i see this on first launch.

    Screen Shot 2019-06-19 at 12 44 56 pm
  2. But after you scrolled away, and then scrolled back to start, that same week view displayed like this

    Screen Shot 2019-06-19 at 12 45 09 pm

I can see that the 16th now looks black colored even though nothing supposed to change. Am i correct in saying that this is the problem you are experiencing?

joseph-francis commented 5 years ago

@patchthecode You're correct in saying the problem that I'm experiencing for week view.

@bivant thank you for suggesting that idea. @patchthecode has already attached the good and bad screenshot. So, I'm going to attach the month view.

Good (need this one)

Screen Shot 2019-06-19 at 3 39 04 PM

Bad

Screen Shot 2019-06-19 at 3 39 24 PM

Also, if you take a closer look at the date "20", you can see that the circle view is not aligned in the center. You can also see that the "20th" date's circle view is aligned in the center in the "Good" screenshot. It messess up after scrolling too.

Screen Shot 2019-06-19 at 3 44 28 PM

Also, note that the problems with each date vary with every scroll.


A combined problem of week view and month view - look at dates "19", "20" and "22"

After scrolling is done in a month view (messed up month view has resulted), when converted back to week view gives the styling as shown below. The problem is that the circle view is not in the center.

Screen Shot 2019-06-19 at 3 53 41 PM

Are screenshots good enough or would you like me to make another video?

patchthecode commented 5 years ago

I believe that the issue with formatting of cells disappearing/disappearing for both week view and month view is the same issue.

So let's fix it for the week view, and it should be gone for the month. I believe the problem is due to code issue.

On the weekview, when you scroll back and forth, do the configuration of the cell change to something different on every scroll? or does it change only once on scroll, and and stays that way on each scroll.

If it changes every time, then this is a cell reuse problem where the cell is not reset correctly. It it changes only once, then there is a problem with the configure(cell function.

If it is a configure(cell problem, you can find out by putting a print statement here

} else {
            attributes(forCell: cell, withStyle: .regular)
            print(cellState.text)
        }

If it prints the 16th, then you can put breakpoint to see why your code is going through that code route.

joseph-francis commented 5 years ago

Thank you, the cell showing the wrong attribute problem is fixed. The problem was with my code.

So, there’s only one more problem left. Do you know why the circle view is not in the center when it’s scrolled? I really appreciate your help; I have been trying to fix this problem for a week. Thank you so much.

patchthecode commented 5 years ago

can you show a screen shot of the bad circle? It all looks good from my end. But then again im a blunt coder. Design skills lacking.

joseph-francis commented 5 years ago

Nah, you're obviously a great coder lol

Screen Shot 2019-06-22 at 10 17 02 AM

Try comparing 26th and 27th and you can see that the circle is not aligned in the center. This problem does not happen when scrolled in week view. It only happens in month view after re-scrolling to a previously displayed month.

It's a tiny problem, but it's so annoying.

patchthecode commented 5 years ago

Ok, i see the issue.

But here is the thing, this library is very bare under the hood. It is just a UICollectionView with zero fancy code. I only wrapped custom date functions around it. Therefore things like constraint positioning etc is directly handled by the under-the-hood- collectionView.

If the positioning of cells are in weird positions this makes me think that you may be using some sort of layout update code, but not on the main thread (this is only a guess though). If you have any layout code relating to updating the calenda, then can you try this?

DispatchQueue.main.async {
   calendarLayout code
}

Can you try that and let meknow if the problem goes away? If it does not, then I would need a sample app demonstrating the issue because I am not an animation expert. You dont have to give me your production project, just create a new blank empty project demonstrating only the problem. This will help me see what the issue is.

patchthecode commented 5 years ago

And dont worry how small the bug is. In fact i think that misalignment issue is a big issue. It does not look good.

joseph-francis commented 5 years ago

Hey! First of all, thank you sooooooo much! I didn't get a chance to work on this till this evening, and I was able to fix the issue. I had a layout update and it was also running on the main thread. The problem was that I was messing with the frame property. So, I recreated the constraints using NSLayoutConstraints. That solved the issue.

Once again, thank you so much for your guidance, help, and your time. I truly appreciate your help!

patchthecode commented 5 years ago

@joseph-francis awesome. glad it was resolved. Have a good one. Will close this.