Blame view

Pods/JTAppleCalendar/Sources/JTAppleCalendarView.swift 9.08 KB
75d24c15   yangbin   123
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
  //
  //  JTAppleCalendarView.swift
  //
  //  Copyright (c) 2016-2017 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar)
  //
  //  Permission is hereby granted, free of charge, to any person obtaining a copy
  //  of this software and associated documentation files (the "Software"), to deal
  //  in the Software without restriction, including without limitation the rights
  //  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  //  copies of the Software, and to permit persons to whom the Software is
  //  furnished to do so, subject to the following conditions:
  //
  //  The above copyright notice and this permission notice shall be included in
  //  all copies or substantial portions of the Software.
  //
  //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  //  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  //  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  //  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  //  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  //  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  //  THE SOFTWARE.
  //
  
  let maxNumberOfDaysInWeek = 7 // Should not be changed
  let maxNumberOfRowsPerMonth = 6 // Should not be changed
  let developerErrorMessage = "There was an error in this code section. Please contact the developer on GitHub"
  let decorationViewID = "Are you ready for the life after this one?"
  let errorDelta: CGFloat = 0.0000001
  
  
  /// An instance of JTAppleCalendarView (or simply, a calendar view) is a
  /// means for displaying and interacting with a gridstyle layout of date-cells
  open class JTAppleCalendarView: UICollectionView {
      
      /// Configures the size of your date cells
      @IBInspectable open var cellSize: CGFloat = 0 {
          didSet {
              if oldValue == cellSize { return }
              calendarViewLayout.invalidateLayout()
          }
      }
      
      /// The scroll direction of the sections in JTAppleCalendar.
      open var scrollDirection: UICollectionView.ScrollDirection!
      
      /// The configuration parameters setup by the developer in the confogureCalendar function
      open var cachedConfiguration: ConfigurationParameters? { return _cachedConfiguration }
      
      /// Enables/Disables the stretching of date cells. When enabled cells will stretch to fit the width of a month in case of a <= 5 row month.
      open var allowsDateCellStretching = true
      
      /// Alerts the calendar that range selection will be checked. If you are
      /// not using rangeSelection and you enable this,
      /// then whenever you click on a datecell, you may notice a very fast
      /// refreshing of the date-cells both left and right of the cell you
      /// just selected.
      open var isRangeSelectionUsed: Bool = false
      
      /// The object that acts as the delegate of the calendar view.
      weak open var calendarDelegate: JTAppleCalendarViewDelegate? {
          didSet { lastMonthSize = sizesForMonthSection() }
      }
      
      /// The object that acts as the data source of the calendar view.
      weak open var calendarDataSource: JTAppleCalendarViewDataSource? {
          didSet { setupMonthInfoAndMap() } // Refetch the data source for a data source change
      }
      
      var lastSavedContentOffset: CGFloat    = 0.0
      var triggerScrollToDateDelegate: Bool? = true
      var isScrollInProgress                 = false
      var isReloadDataInProgress             = false
      
      // keeps track of if didEndScroll is not yet completed. If isStillScrolling
      var didEndScollCount = 0
      // Keeps track of scroll target location. If isScrolling, and user taps while scrolling
      var endScrollTargetLocation: CGFloat = 0
      
      var generalDelayedExecutionClosure: [(() -> Void)] = []
      var scrollDelayedExecutionClosure: [(() -> Void)]  = []
      
      let dateGenerator = JTAppleDateConfigGenerator()
      
      /// Implemented by subclasses to initialize a new object (the receiver) immediately after memory for it has been allocated.
      public init() {
          super.init(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
          setupNewLayout(from: collectionViewLayout as! JTAppleCalendarLayoutProtocol)
      }
      
      /// Initializes and returns a newly allocated collection view object with the specified frame and layout.
      @available(*, unavailable, message: "Please use JTAppleCalendarView() instead. It manages its own layout.")
      public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
          super.init(frame: frame, collectionViewLayout: UICollectionViewFlowLayout())
          setupNewLayout(from: collectionViewLayout as! JTAppleCalendarLayoutProtocol)
      }
      
      /// Initializes using decoder object
      required public init?(coder aDecoder: NSCoder) {
          super.init(coder: aDecoder)
          setupNewLayout(from: collectionViewLayout as! JTAppleCalendarLayoutProtocol)
      }
      
      // Configuration parameters from the dataSource
      var _cachedConfiguration: ConfigurationParameters!
      // Set the start of the month
      var startOfMonthCache: Date!
      // Set the end of month
      var endOfMonthCache: Date!
      var selectedCellData: [IndexPath:SelectedCellData] = [:]
      var pathsToReload: Set<IndexPath> = [] //Paths to reload because of prefetched cells
      
      var anchorDate: Date?
      
      var requestedContentOffset: CGPoint {
          var retval = CGPoint(x: -contentInset.left, y: -contentInset.top)
          guard let date = anchorDate else { return retval }
          
          // reset the initial scroll date once used.
          anchorDate = nil
          
          // Ensure date is within valid boundary
          let components = calendar.dateComponents([.year, .month, .day], from: date)
          let firstDayOfDate = calendar.date(from: components)!
          if !((firstDayOfDate >= startOfMonthCache!) && (firstDayOfDate <= endOfMonthCache!)) { return retval }
          
          // Get valid indexPath of date to scroll to
          let retrievedPathsFromDates = pathsFromDates([date])
          if retrievedPathsFromDates.isEmpty { return retval }
          let sectionIndexPath = pathsFromDates([date])[0]
          
          
          if calendarViewLayout.thereAreHeaders && scrollDirection == .vertical {
              let indexPath = IndexPath(item: 0, section: sectionIndexPath.section)
              guard let attributes = calendarViewLayout.layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: indexPath) else { return retval }
              
              let maxYCalendarOffset = max(0, self.contentSize.height - self.frame.size.height)
              retval = CGPoint(x: attributes.frame.origin.x,y: min(maxYCalendarOffset, attributes.frame.origin.y))
              //            if self.scrollDirection == .horizontal { topOfHeader.x += extraAddedOffset} else { topOfHeader.y += extraAddedOffset }
              
          } else {
              switch scrollingMode {
              case .stopAtEach, .stopAtEachSection, .stopAtEachCalendarFrame:
                  if scrollDirection == .horizontal || (scrollDirection == .vertical && !calendarViewLayout.thereAreHeaders) {
                      retval = self.targetPointForItemAt(indexPath: sectionIndexPath) ?? retval
                  }
              default:
                  break
              }
          }
          return retval
      }
      
      open var sectionInset: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
      open var minimumInteritemSpacing: CGFloat = 0
      open var minimumLineSpacing: CGFloat = 0
      
      lazy var theData: CalendarData = {
          return self.setupMonthInfoDataForStartAndEndDate()
      }()
      
      var lastMonthSize: [AnyHashable:CGFloat] = [:]
      
      var monthMap: [Int: Int] {
          get { return theData.sectionToMonthMap }
          set { theData.sectionToMonthMap = monthMap }
      }
  
      var decelerationRateMatchingScrollingMode: CGFloat {
          switch scrollingMode {
          case .stopAtEachCalendarFrame: return UIScrollView.DecelerationRate.fast.rawValue
          case .stopAtEach, .stopAtEachSection: return UIScrollView.DecelerationRate.fast.rawValue
          case .nonStopToSection, .nonStopToCell, .nonStopTo, .none: return UIScrollView.DecelerationRate.normal.rawValue
          }
      }
  
      /// Configure the scrolling behavior
      open var scrollingMode: ScrollingMode = .stopAtEachCalendarFrame {
          didSet {
              decelerationRate = UIScrollView.DecelerationRate(rawValue: decelerationRateMatchingScrollingMode)
              #if os(iOS)
                  switch scrollingMode {
                  case .stopAtEachCalendarFrame:
                      isPagingEnabled = true
                  default:
                      isPagingEnabled = false
                  }
              #endif
          }
      }
  }
  
  @available(iOS 9.0, *)
  extension JTAppleCalendarView {
      /// A semantic description of the views contents, used to determine whether the view should be flipped when switching between left-to-right and right-to-left layouts.
      
      open override var semanticContentAttribute: UISemanticContentAttribute {
          didSet {
              transform.a = semanticContentAttribute == .forceRightToLeft ? -1 : 1
          }
      }
  }