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 809 forks source link

Foating point rounding error when using scrollingMode = stopAtEachCalendarFrame #1248

Open markusfi opened 4 years ago

markusfi commented 4 years ago

Thank you all for this great library which helps a lot in my current project! I got a problem in my project today and I traced it down to a floating point rounding problem in JtAppleCalendar.

I found that targetPointForItemAt as defined in JTACMonthQueryFunctions.swift (extension JTACMonthView) will not work for all calendar width values if you use scrollingMode = stopAtEachCalendarFrame.

Reason is that you will get floating point rounding errors when using floor(frameSection) in line 67:

// Determines the CGPoint of an index path. The point will vary depending on the scrollingMode
func targetPointForItemAt(indexPath: IndexPath, preferredScrollPosition: UICollectionView.ScrollPosition? = nil) -> CGPoint? 
...
  switch scrollingMode {
     case .stopAtEachCalendarFrame, .stopAtEach, .nonStopTo:
         let frameSection = theTargetContentOffset / fixedScrollSize
         let roundedFrameSection = floor(frameSection)
         if scrollDirection == .horizontal {
             x = roundedFrameSection * fixedScrollSize
         } else {
             // vertical is fixed scroll segments because here, we're using stop at frame and custom fixed size
             y = roundedFrameSection * fixedScrollSize
         }

Say you are at indexPath.section = 66, your fixedScrollSize is 344.3333333333333 for whatever reason (it is in my case) and your theTargetContentOffset = 22725.99999999999 then frameSection will be 65.99999999999997 (but should be 66) and the roundedFrameSection = floor(frameSection) will be 65 which is off 1 section or page however you call it.

So for scrollingMode = stopAtEachCalendarFrame it would be much easier to change the code starting line 64 in file JTACMonthQueryFunctions.swift to:

 switch scrollingMode {
     case .stopAtEachCalendarFrame:
         if scrollDirection == .horizontal {
             x = CGFloat(indexPath.section) * fixedScrollSize
         } else {
             y = CGFloat(indexPath.section) * fixedScrollSize
         }
     case .stopAtEachSection, .nonStopToSection: 

Because all the calculation does is finding the section number to scroll to...

You probably could also simplify the other scrollingModes, because in those cases fixedScrollSize would be the same as theTargetContentOffset, so the result would always be 1, but I have not tested these.

What do you think?

markusfi commented 4 years ago

It seems this is more complicated than I thought and my proposed changes will not solve this issue, but introduce some other problems when scrolling...