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 view’s 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
}
}
}
|