Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strange behaviour on range selection [Bug version 6] #431

Closed
giacmarangoni opened this issue May 26, 2017 · 15 comments
Closed

Strange behaviour on range selection [Bug version 6] #431

giacmarangoni opened this issue May 26, 2017 · 15 comments

Comments

@giacmarangoni
Copy link

giacmarangoni commented May 26, 2017

Hi there, I really want to thank you for this awesome library. I implemented a calendar view with one single range selection at a time and everything is working fine. Unfortunately I'm facing a very strange behaviour on dates near to outdate cells. I need to hide outdates cell and I'm using JTAppleCalendar 6.1.6 in order to set minimum deployment target to 8.0.
Here a little video of my problem:

ezgif-3-eb85b97f52

//
//  ViewController.swift
//  CalendarTutorial
//

import UIKit
import JTAppleCalendar

class ViewController: UIViewController, JTAppleCalendarViewDataSource, JTAppleCalendarViewDelegate {

    @IBOutlet weak var calendarView: JTAppleCalendarView!
    @IBOutlet weak var headerView: UIView!
    
    let monthFormatter = DateFormatter()
    var currentCalendar = Calendar.current
    
    var checkIn: Date? = nil
    var checkOut: Date? = nil
    var isRangeSelected = false
    
    let colorBelongsTo = UIColor(hex: "2C3135")
    let colorNotBelongTo = UIColor(hex: "B0B0B0")
    let colorSelected = UIColor.white

    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.navigationItem.setSimpleTitle(string: "SELECT DATES")
        
        headerView.backgroundColor = UIColor.init(colorLiteralRed: 43/255.0, green: 48/255.0, blue: 60/255.0, alpha: 1/1.0)
        
        calendarView.dataSource = self
        calendarView.delegate = self
        calendarView.scrollDirection = .vertical
        calendarView.scrollingMode = .none
        calendarView.allowsMultipleSelection = true
        calendarView.rangeSelectionWillBeUsed = true
        calendarView.registerCellViewXib(file: "CellView")
        calendarView.registerHeaderView(xibFileNames: ["CalendarHeaderView"])
        calendarView.cellInset = CGPoint(x: 0, y: 0)
        
        let screenWidth = self.view.frame.size.width
        let itemSize = Double(screenWidth) / 6.5
        calendarView.itemSize = CGFloat(itemSize)
                
        monthFormatter.dateFormat = "MMMM yyyy"
    }
    
    func configureCalendar(_ calendar: JTAppleCalendarView) -> ConfigurationParameters {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy MM dd"
        let startDate = formatter.date(from: "2017 05 24")!
        let endDate = formatter.date(from: "2019 05 24")!
    
        let parameters = ConfigurationParameters(startDate: startDate,
                                                 endDate: endDate,
                                                 numberOfRows: 6,
                                                 calendar: currentCalendar,
                                                 generateInDates: .forAllMonths,
                                                 generateOutDates: .off,
                                                 firstDayOfWeek: .monday)
        return parameters
    }
        
    func calendar(_ calendar: JTAppleCalendarView, willDisplayCell cell: JTAppleDayCellView, date: Date, cellState: CellState) {
        let myCustomCell = cell as! CellView
        
        myCustomCell.dayLabel.text = cellState.text
        
        handleCellTextColor(view: cell, cellState: cellState, state: cellState.dateBelongsTo != .thisMonth)
        handleCellSelection(view: cell, cellState: cellState, state: cellState.dateBelongsTo != .thisMonth)
    }
    
    func calendar(_ calendar: JTAppleCalendarView, sectionHeaderSizeFor range: (start: Date, end: Date), belongingTo month: Int) -> CGSize {
        return CGSize(width: self.view.frame.size.width, height: 92)
    }

    func calendar(_ calendar: JTAppleCalendarView, willDisplaySectionHeader header: JTAppleHeaderView, range: (start: Date, end: Date), identifier: String) {
        let headerCell = (header as? CalendarHeaderView)
        headerCell?.title.text = monthFormatter.string(from: range.start).capitalized
    }
    
    func calendar(_ calendar: JTAppleCalendarView, didSelectDate date: Date, cell: JTAppleDayCellView?, cellState: CellState) {
        if isRangeSelected {
            calendarView.deselectDates(from: checkIn!, to: checkOut!, triggerSelectionDelegate: false)
            checkIn = date
            checkOut = nil
            isRangeSelected = false
            calendarView.selectDates([checkIn!], triggerSelectionDelegate: false, keepSelectionIfMultiSelectionAllowed: true)
        } else {
            if checkIn == nil {
                checkIn = date
                calendarView.selectDates([checkIn!], triggerSelectionDelegate: false, keepSelectionIfMultiSelectionAllowed: true)
            } else {
                if date < checkIn! {
                    calendarView.deselectDates(from: checkIn!, to: checkIn!, triggerSelectionDelegate: false)
                    checkIn = date
                    checkOut = nil
                    isRangeSelected = false
                    calendarView.selectDates([checkIn!], triggerSelectionDelegate: false, keepSelectionIfMultiSelectionAllowed: true)
                } else {
                    checkOut = date
                    isRangeSelected = true
                    calendarView.selectDates(from: checkIn!, to: checkOut!,  triggerSelectionDelegate: false, keepSelectionIfMultiSelectionAllowed: true)
                }
            }
        }
    }
    
    func calendar(_ calendar: JTAppleCalendarView, didDeselectDate date: Date, cell: JTAppleDayCellView?, cellState: CellState) {
        calendarView.deselectAllDates(triggerSelectionDelegate: false)
        if !isRangeSelected {
            checkIn = date
        } else {
            checkIn = date
            checkOut = nil
            isRangeSelected = false
        }
        calendarView.selectDates([checkIn!], triggerSelectionDelegate: false, keepSelectionIfMultiSelectionAllowed: true)
    }
    
    func handleCellTextColor(view: JTAppleDayCellView?, cellState: CellState, state: Bool) {
        guard let myCustomCell = view as? CellView  else {
            return
        }
        
        if cellState.isSelected {
            if cellState.date == checkIn || cellState.date == checkOut {
                myCustomCell.dayLabel.textColor = colorSelected
            } else if cellState.date > checkIn! && cellState.date < checkOut! {
                myCustomCell.dayLabel.textColor = colorBelongsTo
            }
        } else {
            if cellState.dateBelongsTo == .thisMonth {
                myCustomCell.dayLabel.textColor = colorBelongsTo
            } else {
                myCustomCell.dayLabel.textColor = colorNotBelongTo
            }
        }
        
        myCustomCell.isHidden = state
    }
    
    func handleCellSelection(view: JTAppleDayCellView?, cellState: CellState, state: Bool) {
        guard let myCustomCell = view as? CellView  else {
            return
        }
        
        if cellState.isSelected {
            if cellState.date == checkIn {
                if checkOut == nil {
                    myCustomCell.selectedView.image = UIImage(named: "SingleSelection")
                } else {
                    myCustomCell.selectedView.image = UIImage(named: "LeftSelection")
                }
            } else if cellState.date == checkOut {
                myCustomCell.selectedView.image = UIImage(named: "RightSelection")
            } else if cellState.date > checkIn! && cellState.date < checkOut! {
                myCustomCell.selectedView.image = UIImage(named: "ShapeSelection")
            }
        } else {
            myCustomCell.selectedView.image = UIImage()
        }
        
        myCustomCell.isHidden = state
    }
}
@patchthecode
Copy link
Owner

I have fixed many issues which occurs in version 6 in version 7.
Tell you what, i'll make version 7 compatible with iOS 8.

One thing, so when you select the 31 what is the crash message?

if youre online, you can join me here -> https://gitter.im/patchthecode/JTAppleCalendar

@giacmarangoni
Copy link
Author

giacmarangoni commented May 26, 2017

Currently I can't join you, but here you can see the crash message:
schermata 2017-05-26 alle 15 34 57

I think It's related to cell reuse. If I try to disable user interaction only for outbound cells, all cells in my calendar are disabled.

@patchthecode
Copy link
Owner

ok the error seems to be because you are force unwrapping a value.
Doesnt seem to be a calendar issue thankfully.

either you variable checkIn is nill or your variable checkOut is nil. And because you are force-unwrapping it with the ! symbol, it crashes.

In swift you should always guard let or if let or at the very least check to see that your nillable variables are not nil.
Let me know if this helped.

@giacmarangoni
Copy link
Author

giacmarangoni commented May 26, 2017

Thank you for answer. I know what you are talking about, but when I debug I pointed out that for other cells It works. This problem occurs when cell should be outdate on the next month. It seems to be something related to cell reuse. It happens for 29-30-31. If you want I could share my custom projet with you.

@patchthecode
Copy link
Owner

@giacmarangoni sure you can create it on a github repository, and i'll check it out.
or you can zip it and send it to [email protected]

@giacmarangoni
Copy link
Author

giacmarangoni commented May 26, 2017

@patchthecode I sent you a zip to your mail address. Thank you.

@patchthecode
Copy link
Owner

patchthecode commented May 26, 2017

Hey, the problem is that you are not setting your checkIn variable.

when you click on the 31st, the willDisplayCell function will get called.
When it gets called, the handleCellTextColor gets called.
When this gets called, your checkIn/checkOut date is still nil.

Therefore it crashes when your force unwrapp it.
This is expected.

@giacmarangoni
Copy link
Author

giacmarangoni commented May 29, 2017

Sorry for the delay. I just checked your answer, and I try to improve my debugging process. I set up checkIn / checkOut to a custom date range in viewDidLoad and I try to trigger automatic selection like the following:

checkIn = Date()
checkOut = Calendar.current.date(byAdding: .day, value: 7, to: Date())!
calendarView.selectDates(from: checkIn!, to: checkOut!, triggerSelectionDelegate: true)

Now if you try to print checkIn / checkOut inside handleCellTextColor, you will see that it will crash only for last days of month (It prints same values for each cell visible in month, except for last). I cannot figure out if this problem is linked with my code or with this older versione of your awesome library. Is there a different approach to implement range selection? Thank you

@patchthecode patchthecode self-assigned this May 30, 2017
@patchthecode patchthecode changed the title Strange behaviour on range selection Strange behaviour on range selection [Bug version 6] May 30, 2017
@patchthecode
Copy link
Owner

patchthecode commented May 30, 2017

Hey, i had time to look at it
and it does happen to look like a bug.

Your didSelect function should be called first but it seems that i call the willDisplayCell first.
Can you try this for me?

in the UICollectionViewDelegates file, look for this function ->

public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

then look for this line

if pathsToReload.count > 0 {
   batchReloadIndexPaths(pathsToReload)
}

delegate.calendar(self, didSelectDate: infoOfDateSelectedByUser.date, cell: selectedCell?.view, cellState: cellState)

change it to this instead:

delegate.calendar(self, didSelectDate: infoOfDateSelectedByUser.date, cell: selectedCell?.view, cellState: cellState)
if pathsToReload.count > 0 {
   batchReloadIndexPaths(pathsToReload)
}

Let me know what happens.

@giacmarangoni
Copy link
Author

giacmarangoni commented May 30, 2017

Hi @patchthecode I just tried what you suggested and It works. Should it be done also in didDeselectItemAt?

@patchthecode
Copy link
Owner

patchthecode commented May 31, 2017

OK, if it works, let me look into what can be done.
but yes, try it for the other functions as well.
i think i might have called it in the wrong order by accident.
Im just surprised that no one mentioned this to me all the way into version 6.

Let me know if the code change there works as well.

@patchthecode
Copy link
Owner

patchthecode commented May 31, 2017

I have made the code change on master branch in version 7.0
And ive made the changes on branch 6.1.6 as well
closing this issue momentarily

@giacmarangoni
Copy link
Author

I'm very happy I helped you to find a bug in your previous version of this awesome control. Thank you @patchthecode. I would like to ask you if you should plan to support iOS 8 for latest release of JTAppleCalendar.

@patchthecode
Copy link
Owner

Yes, i do plan to do this. Its a very small change to do this.
I can look at it this weekend.

@patchthecode
Copy link
Owner

@giacmarangoni thanks very much for finding this error.
I'm still sort of surprised no one noticed it.

the fix is in and version 7.0.3 has been released with it.
closing this issue now.

In regards to making this iOS 8 compatible, I work on this over the weekend.
I've created a new issue for it here -> #434

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants