bm-core-ui 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1624 -0
- package/LICENSE +21 -0
- package/README.md +145 -0
- package/build/@types/index.d.ts +14078 -0
- package/build/BMCodeEditor/BMCodeEditor.js +707 -0
- package/build/BMCollectionView/BMCollectionView.js +5861 -0
- package/build/BMCollectionView/BMCollectionViewCell.js +688 -0
- package/build/BMCollectionView/BMCollectionViewFlowLayout.js +4467 -0
- package/build/BMCollectionView/BMCollectionViewLayout.js +830 -0
- package/build/BMCollectionView/BMCollectionViewLayoutAttributes.js +673 -0
- package/build/BMCollectionView/BMCollectionViewMasonryLayout.js +491 -0
- package/build/BMCollectionView/BMCollectionViewStackLayout.js +634 -0
- package/build/BMCollectionView/BMCollectionViewTileLayout.js +1121 -0
- package/build/BMCoreUI.css +2833 -0
- package/build/BMView/BMAttributedLabelView.js +310 -0
- package/build/BMView/BMLayoutConstraint_v2.5.js +1813 -0
- package/build/BMView/BMLayoutGuide.js +192 -0
- package/build/BMView/BMLayoutSizeClass.js +466 -0
- package/build/BMView/BMMenu.js +574 -0
- package/build/BMView/BMScrollView.js +247 -0
- package/build/BMView/BMTextField.js +512 -0
- package/build/BMView/BMTextFieldDelegate.js +71 -0
- package/build/BMView/BMView_v2.5.js +3572 -0
- package/build/BMView/BMViewport.js +221 -0
- package/build/BMViewLayoutEditor/BMLayoutEditor.js +6117 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorConstraintSettings.js +417 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorDelegate.js +59 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettingCells.js +1653 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettings.js +1480 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettingsComplexCells.js +431 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettingsDelegate.js +46 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorVariablesController.js +460 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorViewGroupSettings.js +293 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorViewSettings.js +382 -0
- package/build/BMViewLayoutEditor/BMLayoutVariableProvider.js +202 -0
- package/build/BMWindow/BMConfirmationPopup.js +477 -0
- package/build/BMWindow/BMKeyboardShortcut.js +151 -0
- package/build/BMWindow/BMPopover/BMPopover.js +492 -0
- package/build/BMWindow/BMToolWindow.js +71 -0
- package/build/BMWindow/BMWindow.js +2000 -0
- package/build/Core/BMAnimationContext.js +1181 -0
- package/build/Core/BMColor.js +991 -0
- package/build/Core/BMCoreUI.js +470 -0
- package/build/Core/BMFunctionCollection.js +110 -0
- package/build/Core/BMIndexPath.js +165 -0
- package/build/Core/BMInset.js +138 -0
- package/build/Core/BMKeyPath.js +100 -0
- package/build/Core/BMPoint.js +291 -0
- package/build/Core/BMRect.js +556 -0
- package/build/Core/BMSize.js +137 -0
- package/build/iScroll/LICENSE +22 -0
- package/build/iScroll/iscroll-probe.js +2154 -0
- package/build/images/AlignBottom.png +0 -0
- package/build/images/AlignCenterX.png +0 -0
- package/build/images/AlignCenterY.png +0 -0
- package/build/images/AlignLeading.png +0 -0
- package/build/images/AlignTop.png +0 -0
- package/build/images/AlignTrailing.png +0 -0
- package/build/images/AllConstraints.png +0 -0
- package/build/images/BottomConstraint.png +0 -0
- package/build/images/CenterXConstraint.png +0 -0
- package/build/images/CenterYConstraint.png +0 -0
- package/build/images/CoreUI2.png +0 -0
- package/build/images/CoreUI2@2x.png +0 -0
- package/build/images/Desktop.png +0 -0
- package/build/images/DesktopMini.png +0 -0
- package/build/images/EqualHeight.png +0 -0
- package/build/images/EqualHorizontalSpacing.png +0 -0
- package/build/images/EqualHorizontalSpacingInSuperview.png +0 -0
- package/build/images/EqualVerticalSpacing.png +0 -0
- package/build/images/EqualVerticalSpacingInSuperview.png +0 -0
- package/build/images/EqualWidth.png +0 -0
- package/build/images/HeightConstraint.png +0 -0
- package/build/images/InactiveConstraints.png +0 -0
- package/build/images/Layout.png +0 -0
- package/build/images/LayoutVariables.png +0 -0
- package/build/images/LeftConstraint.png +0 -0
- package/build/images/OwnConstraints.png +0 -0
- package/build/images/Phone.png +0 -0
- package/build/images/PhoneLandscape.png +0 -0
- package/build/images/PhoneLandscapeMini.png +0 -0
- package/build/images/PhoneMini.png +0 -0
- package/build/images/PhonePortrait.png +0 -0
- package/build/images/PhonePortraitMini.png +0 -0
- package/build/images/Properties.png +0 -0
- package/build/images/RightConstraint.png +0 -0
- package/build/images/SubviewConstraints.png +0 -0
- package/build/images/Tablet.png +0 -0
- package/build/images/TabletLandscape.png +0 -0
- package/build/images/TabletLandscapeMini.png +0 -0
- package/build/images/TabletMini.png +0 -0
- package/build/images/TabletPortrait.png +0 -0
- package/build/images/TabletPortraitMini.png +0 -0
- package/build/images/TopConstraint.png +0 -0
- package/build/images/WidthConstraint.png +0 -0
- package/build/index.js +40 -0
- package/lib/@types/BMCoreUI.min.d.ts +14078 -0
- package/lib/BMCoreUI.min.js +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,4467 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {YES, NO, BMExtend} from '../Core/BMCoreUI'
|
|
4
|
+
import {BMInsetMake} from '../Core/BMInset'
|
|
5
|
+
import {BMSizeMake} from '../Core/BMSize'
|
|
6
|
+
import {BMRectMake} from '../Core/BMRect'
|
|
7
|
+
import {BMIndexPathMakeWithIndexes, BMIndexPathMakeWithRow} from '../Core/BMIndexPath'
|
|
8
|
+
import {BMCollectionViewLayoutAttributesMakeForCellAtIndexPath, BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier} from './BMCollectionViewLayoutAttributes'
|
|
9
|
+
import {BMLayoutConstraint, BMLayoutAttribute, BMLayoutConstraintRelation} from '../BMView/BMLayoutConstraint_v2.5'
|
|
10
|
+
import {BMCollectionViewLayout} from './BMCollectionViewLayout'
|
|
11
|
+
|
|
12
|
+
// When set to YES, this will cause flow layout to log messages to the console when the layout size changes as a
|
|
13
|
+
// result of performing cell measurements
|
|
14
|
+
const BM_AUTOMATIC_CELL_SIZE_MESSAGES = NO;
|
|
15
|
+
|
|
16
|
+
// @type BMCollectionViewTableLayoutSupplementaryView
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Constants representing the types of supplementary views that table layouts support.
|
|
20
|
+
*/
|
|
21
|
+
export var BMCollectionViewTableLayoutSupplementaryView = Object.freeze({
|
|
22
|
+
/**
|
|
23
|
+
* Indicates that this supplementary view is a footer.
|
|
24
|
+
*/
|
|
25
|
+
Footer: "Footer", // <enum>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Indicates that this supplementary view is a header.
|
|
29
|
+
*/
|
|
30
|
+
Header: "Header", // <enum>
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Indicates that this supplementary is an empty content view.
|
|
34
|
+
*/
|
|
35
|
+
Empty: "Empty" // <enum>
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// @type
|
|
39
|
+
|
|
40
|
+
// @type BMCollectionViewTableLayout extends BMCollectionViewLayout
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @deprecated Use `BMCollectionViewFlowLayout` with the `maximumCellsPerRow` property set to `1`.
|
|
44
|
+
* --------------------------------------
|
|
45
|
+
* A basic layout implementation, the BMCollectionViewTableLayout will lay out its elements as a list where each row can have
|
|
46
|
+
* either a fixed height or a variable height.
|
|
47
|
+
*
|
|
48
|
+
* If the row heights are variable, the collection must have a delegate that implements the
|
|
49
|
+
* `collectionViewRowHeightForCellAtIndexPath(_, _)` method.
|
|
50
|
+
*
|
|
51
|
+
* Optionally, the table layout may also generate supplementary views for section headers and section footers.
|
|
52
|
+
*/
|
|
53
|
+
export function BMCollectionViewTableLayout() {}; // <constructor>
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A value which may be assigned to the rowHeight property to indicate that the row heights vary by row.
|
|
57
|
+
*/
|
|
58
|
+
export var BMCollectionViewTableLayoutRowHeightVariable = -1; // <Number>
|
|
59
|
+
|
|
60
|
+
BMCollectionViewTableLayout.prototype = BMExtend(Object.create(BMCollectionViewLayout.prototype), {
|
|
61
|
+
constructor: BMCollectionViewTableLayout,
|
|
62
|
+
|
|
63
|
+
// @override - BMCollectionViewLayout
|
|
64
|
+
get supportsCopying() {
|
|
65
|
+
return YES;
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The row height to use for each row.
|
|
70
|
+
* This can either be a specific size in pixels or BMCollectionViewTableLayoutRowHeightVariable if each row can have a different height.
|
|
71
|
+
* If the row heights are variable, the collection must have a delegate that implements the Number collectionViewRowHeightForCellAtIndexPath(BMCollectionView, BMIndexPath) method.
|
|
72
|
+
*/
|
|
73
|
+
_rowHeight: 44, // <Number>
|
|
74
|
+
|
|
75
|
+
get rowHeight() { return this._rowHeight; },
|
|
76
|
+
set rowHeight(rowHeight) {
|
|
77
|
+
this._rowHeight = rowHeight;
|
|
78
|
+
this.invalidateLayout();
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The minimum width to use for each row.
|
|
83
|
+
* If this value is greater than the collection view's frame width, the rows will have this width applied to them.
|
|
84
|
+
*/
|
|
85
|
+
_minimumWidth: 0, // <Number>
|
|
86
|
+
|
|
87
|
+
get minimumWidth() { return this._minimumWidth; },
|
|
88
|
+
set minimumWidth(width) {
|
|
89
|
+
this._minimumWidth = width;
|
|
90
|
+
this.invalidateLayout();
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Determines whether or not the table layout should generate header supplementary views or not.
|
|
95
|
+
*/
|
|
96
|
+
_showsHeaders: NO, // <Boolean>
|
|
97
|
+
|
|
98
|
+
get showsHeaders() { return this._showsHeaders; },
|
|
99
|
+
set showsHeaders(showsHeaders) {
|
|
100
|
+
this._showsHeaders = showsHeaders;
|
|
101
|
+
this.invalidateLayout();
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The height to use for headers. Does not have any effect if showsHeaders is set to NO.
|
|
106
|
+
* This attribute is always 0 if showsHeader is set to NO.
|
|
107
|
+
*/
|
|
108
|
+
_headerHeight: 22, // <Number>
|
|
109
|
+
|
|
110
|
+
get headerHeight() {
|
|
111
|
+
return this._showsHeaders ? this._headerHeight : 0;
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
set headerHeight(headerHeight) {
|
|
115
|
+
this._headerHeight = headerHeight;
|
|
116
|
+
if (this._showsHeader) this.invalidateLayout();
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Determines whether or not the table layout should generate footer supplementary views or not.
|
|
121
|
+
*/
|
|
122
|
+
_showsFooters: NO, // <Boolean>
|
|
123
|
+
|
|
124
|
+
get showsFooters() { return this._showsFooters; },
|
|
125
|
+
set showsFooters(showsFooters) {
|
|
126
|
+
this._showsFooters = showsFooters;
|
|
127
|
+
this.invalidateLayout();
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The height to use for footers. Does not have any effect if showsFooters is set to NO.
|
|
132
|
+
* This attribute is always 0 if showsFooters is set to NO.
|
|
133
|
+
*/
|
|
134
|
+
_footerHeight: 22, // <Number>
|
|
135
|
+
|
|
136
|
+
get footerHeight() {
|
|
137
|
+
return this._showsFooters? this._footerHeight : 0;
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
set footerHeight(footerHeight) {
|
|
141
|
+
this._footerHeight = footerHeight;
|
|
142
|
+
if (this._showsFooters) this.invalidateLayout();
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The insets to apply to each section which control their distance from the collection view edges and from other sections.
|
|
147
|
+
*/
|
|
148
|
+
_sectionInsets: BMInsetMake(), // <BMInset, nullResettable>
|
|
149
|
+
|
|
150
|
+
get sectionInsets() { return this._sectionInsets; },
|
|
151
|
+
set sectionInsets(insets) {
|
|
152
|
+
this._sectionInsets = insets || BMInsetMake();
|
|
153
|
+
this.invalidateLayout();
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* When set to YES, the table layout will stick the header supplementary views to the top edge of the collection view while scrolling.
|
|
158
|
+
* Otherwise, the header supplementary views will scroll with the rest of the content.
|
|
159
|
+
*/
|
|
160
|
+
_pinsHeadersToContentEdge: NO, // <Boolean>
|
|
161
|
+
|
|
162
|
+
get pinsHeadersToContentEdge() { return this._pinsHeadersToContentEdge; },
|
|
163
|
+
set pinsHeadersToContentEdge(pins) {
|
|
164
|
+
this._pinsHeadersToContentEdge = pins;
|
|
165
|
+
this.invalidateLayout();
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* When set to YES, the table layout will stick the footer supplementary views to the bottom edge of the collection view while scrolling.
|
|
170
|
+
* Otherwise, the footer supplementary views will scroll with the rest of the content.
|
|
171
|
+
*/
|
|
172
|
+
_pinsFootersToContentEdge: NO, // <Boolean>
|
|
173
|
+
|
|
174
|
+
get pinsFootersToContentEdge() { return this._pinsFootersToContentEdge; },
|
|
175
|
+
set pinsFootersToContentEdge(pins) {
|
|
176
|
+
this._pinsFootersToContentEdge = pins;
|
|
177
|
+
this.invalidateLayout();
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// The cached content size.
|
|
181
|
+
_cachedContentSize: undefined, // <BMSize>
|
|
182
|
+
|
|
183
|
+
// @override - BMCollectionViewLayout
|
|
184
|
+
constraintsForMeasuringCell(cell, {atIndexPath}) {
|
|
185
|
+
// In table layout, cells are required to be as wide as the collection view's frame
|
|
186
|
+
return [BMLayoutConstraint.constraintWithView(cell, {attribute: BMLayoutAttribute.Width, constant: this.collectionView.frame.size.width})]
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
prepareLayout() {
|
|
190
|
+
return this.contentSize();
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// @override
|
|
194
|
+
contentSize: function () {
|
|
195
|
+
|
|
196
|
+
// Each section adds height for its insets and headers and footers
|
|
197
|
+
var insetTop = this._sectionInsets.top;
|
|
198
|
+
var insetBottom = this._sectionInsets.bottom;
|
|
199
|
+
|
|
200
|
+
var headerHeight = this.headerHeight;
|
|
201
|
+
var footerHeight = this.footerHeight;
|
|
202
|
+
|
|
203
|
+
var height = 0;
|
|
204
|
+
|
|
205
|
+
//var sections = this.collectionView.sections;
|
|
206
|
+
var sectionCount = this.collectionView.numberOfSections();
|
|
207
|
+
height += sectionCount * (headerHeight + footerHeight + insetTop + insetBottom);
|
|
208
|
+
|
|
209
|
+
if (this._rowHeight >= 0) {
|
|
210
|
+
|
|
211
|
+
for (var i = 0; i < sectionCount; i++) {
|
|
212
|
+
height += this._rowHeight * this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
|
|
218
|
+
// If the rows have variable heights, the layout must query the delegate for each item's size
|
|
219
|
+
for (var i = 0; i < sectionCount; i++) {
|
|
220
|
+
var itemCount = this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
221
|
+
for (var j = 0; j < itemCount; j++) {
|
|
222
|
+
height += this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(
|
|
223
|
+
this.collectionView,
|
|
224
|
+
this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i})
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this._cachedContentSize = BMSizeMake(
|
|
232
|
+
Math.max(this.collectionView.frame.size.width, this.minimumWidth + this.collectionView.scrollBarSize),
|
|
233
|
+
Math.max(this.collectionView.frame.size.height, height)
|
|
234
|
+
);
|
|
235
|
+
return this._cachedContentSize;
|
|
236
|
+
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
// @override - BMCollectionViewLayout
|
|
240
|
+
shouldInvalidateLayoutForBoundsChange: function (bounds) {
|
|
241
|
+
return this._pinsHeadersToContentEdge || this._pinsFootersToContentEdge;
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
// @override - BMCollectionViewTableLayout
|
|
245
|
+
shouldInvalidateLayoutForFrameChange() {
|
|
246
|
+
// Because the measured size of the cells is dependent upon the size of collection view's frame, in addition
|
|
247
|
+
// to invalidating the layout, the measured size of cells should also be invalidated.
|
|
248
|
+
this.collectionView.invalidateMeasuredSizeOfCells();
|
|
249
|
+
return YES;
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
// @override
|
|
253
|
+
attributesForCellAtIndexPath: function (indexPath) {
|
|
254
|
+
var row = indexPath.row;
|
|
255
|
+
var section = indexPath.section;
|
|
256
|
+
|
|
257
|
+
var sectionPaddingTop = this._sectionInsets.top + this.headerHeight;
|
|
258
|
+
var sectionPaddingBottom = this._sectionInsets.bottom + this.footerHeight;
|
|
259
|
+
|
|
260
|
+
var top = (section + 1) * sectionPaddingTop + section * sectionPaddingBottom;
|
|
261
|
+
|
|
262
|
+
var useOffset = this._cachedContentSize.height > this.collectionView.frame.size.height;
|
|
263
|
+
|
|
264
|
+
if (this._rowHeight >= 0) {
|
|
265
|
+
// The count has to include the current section, so it includes objects that may be in the same section before the current object
|
|
266
|
+
for (var i = 0; i <= section; i++) {
|
|
267
|
+
// If the count has reached the current section, it will stop at the current object rather than the end of the section
|
|
268
|
+
var itemCount = i == section ? row : this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
269
|
+
|
|
270
|
+
top += this._rowHeight * itemCount;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
|
|
275
|
+
// If the rows have variable heights, the layout must query the delegate for each item's size
|
|
276
|
+
|
|
277
|
+
// The count has to include the current section, so it includes objects that may be in the same section before the current object
|
|
278
|
+
for (var i = 0; i <= section; i++) {
|
|
279
|
+
|
|
280
|
+
// If the count has reached the current section, it will stop at the current object rather than the end of the section
|
|
281
|
+
var itemCount = i == section ? row : this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
282
|
+
|
|
283
|
+
for (var j = 0; j < itemCount; j++) {
|
|
284
|
+
top += this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(
|
|
285
|
+
this.collectionView,
|
|
286
|
+
this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i})
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
var frame = BMRectMake(this._sectionInsets.left,
|
|
293
|
+
top,
|
|
294
|
+
Math.max(this.collectionView.frame.size.width, this.minimumWidth + this.collectionView.scrollBarSize) - this._sectionInsets.left - this._sectionInsets.right - (useOffset ? this.collectionView.scrollBarSize : 0),
|
|
295
|
+
this._rowHeight >= 0 ? this._rowHeight : this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(
|
|
296
|
+
this.collectionView,
|
|
297
|
+
indexPath
|
|
298
|
+
)
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
var attributes = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(indexPath);
|
|
302
|
+
attributes.frame = frame;
|
|
303
|
+
|
|
304
|
+
return attributes;
|
|
305
|
+
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Retrieves the base attributes for the given supplementary view, before adjusting for pinned headers or footers.
|
|
310
|
+
* @param identifier <String> The supplementary view's type identifier.
|
|
311
|
+
* {
|
|
312
|
+
* @param atIndexPath <BMIndexPath> The supplementary view's index path.
|
|
313
|
+
* }
|
|
314
|
+
* @return <BMCollectionViewLayoutAttributes> The index path with additional information.
|
|
315
|
+
*/
|
|
316
|
+
attributesForSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
317
|
+
|
|
318
|
+
var useOffset = this._cachedContentSize.height > this.collectionView.frame.size.height;
|
|
319
|
+
|
|
320
|
+
// The empty supplementary view will always ever have one instance with a preset index path
|
|
321
|
+
if (identifier === BMCollectionViewTableLayoutSupplementaryView.Empty) {
|
|
322
|
+
var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
323
|
+
BMCollectionViewTableLayoutSupplementaryView.Empty,
|
|
324
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([0])}
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
attribute.frame = BMRectMake(0, 0, this.collectionView.frame.size.width, this.collectionView.frame.size.height);
|
|
328
|
+
return attribute;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
var section = options.atIndexPath.section;
|
|
332
|
+
|
|
333
|
+
if (section >= this.collectionView.numberOfSections()) return undefined;
|
|
334
|
+
|
|
335
|
+
// The caret moves through the index paths until it reaches the target section
|
|
336
|
+
var caret = this.sectionInsets.top;
|
|
337
|
+
|
|
338
|
+
var hasVariableRowHeights = this._rowHeight < 0;
|
|
339
|
+
var sectionSpacing = this.sectionInsets.top + this.sectionInsets.bottom;
|
|
340
|
+
|
|
341
|
+
var cellPositionX = this.sectionInsets.left;
|
|
342
|
+
var cellWidth = Math.max(this.collectionView.frame.size.width, this.minimumWidth + this.collectionView.scrollBarSize) - this.sectionInsets.left - this.sectionInsets.right - (useOffset ? this.collectionView.scrollBarSize : 0);
|
|
343
|
+
|
|
344
|
+
// These variables are used to verify if the target section is the topmost or bottommost one,
|
|
345
|
+
// to pin their header and footers to collection view edges if needed
|
|
346
|
+
var isTopmostSection = NO;
|
|
347
|
+
var isBottommostSection = NO;
|
|
348
|
+
|
|
349
|
+
var scrollOffset = this.collectionView.scrollOffset;
|
|
350
|
+
var frame = this.collectionView.frame;
|
|
351
|
+
|
|
352
|
+
var topEdge;
|
|
353
|
+
|
|
354
|
+
// Skip all index paths until the target section
|
|
355
|
+
for (var i = 0; i < section; i++) {
|
|
356
|
+
|
|
357
|
+
topEdge = caret + sectionSpacing;
|
|
358
|
+
|
|
359
|
+
// Skip headers, footers and insets
|
|
360
|
+
caret += this.headerHeight + this.footerHeight + sectionSpacing;
|
|
361
|
+
|
|
362
|
+
var objectCount = this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
363
|
+
|
|
364
|
+
// Skip the contents
|
|
365
|
+
if (hasVariableRowHeights) {
|
|
366
|
+
for (var j = 0; j < objectCount; j++) {
|
|
367
|
+
caret += this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(this.collectionView, this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i}));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
caret += objectCount * this._rowHeight;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Verify if this section is the topmost or bottommost section
|
|
376
|
+
if (topEdge < scrollOffset.y && caret > scrollOffset.y) {
|
|
377
|
+
isTopmostSection = YES;
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
isTopmostSection = NO;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (topEdge > scrollOffset.y && caret > scrollOffset.y + frame.size.height) {
|
|
384
|
+
isBottommostSection = YES;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
isBottommostSection = NO;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (identifier === BMCollectionViewTableLayoutSupplementaryView.Header) {
|
|
391
|
+
// For headers, the caret will stop directly at the specified header
|
|
392
|
+
var attributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(BMCollectionViewTableLayoutSupplementaryView.Header, {atIndexPath: options.atIndexPath});
|
|
393
|
+
attributes.frame = BMRectMake(cellPositionX, caret, cellWidth, this.headerHeight);
|
|
394
|
+
|
|
395
|
+
// If this header is in the topmost section and the layout pins headers to content edges, adjust the position of the frame
|
|
396
|
+
if (this._pinsHeadersToContentEdge && isTopmostSection) {
|
|
397
|
+
attributes.frame.origin.y = Math.max(attributes.frame.origin.y, scrollOffset.y);
|
|
398
|
+
// Float the header above the content
|
|
399
|
+
attributes.style.zIndex = 1;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return attributes;
|
|
403
|
+
}
|
|
404
|
+
else if (identifier === BMCollectionViewTableLayoutSupplementaryView.Footer) {
|
|
405
|
+
// For footers, the caret will stop at the end of the section
|
|
406
|
+
|
|
407
|
+
// Skip the contents
|
|
408
|
+
var objectCount = this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
409
|
+
if (hasVariableRowHeights) {
|
|
410
|
+
for (var j = 0; j < objectCount; j++) {
|
|
411
|
+
caret += this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(this.collectionView, this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i}));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
caret += objectCount * this._rowHeight;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// And skip the header as well
|
|
419
|
+
caret += this.headerHeight;
|
|
420
|
+
|
|
421
|
+
var attributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(BMCollectionViewTableLayoutSupplementaryView.Footer, {atIndexPath: options.atIndexPath});
|
|
422
|
+
attributes.frame = BMRectMake(cellPositionX, caret, cellWidth, this.footerHeight);
|
|
423
|
+
|
|
424
|
+
// If this footer is in the bottommost section and the layout pins footers to content edges, adjust the position of the frame
|
|
425
|
+
if (this._pinsHeadersToContentEdge && isBottommostSection) {
|
|
426
|
+
attributes.frame.origin.y = Math.min(attributes.frame.origin.y, scrollOffset.y + this.collectionView.frame.size.height - attributes.frame.size.height);
|
|
427
|
+
// Float the header above the content
|
|
428
|
+
attributes.style.zIndex = 1;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return attributes;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Other types of supplementary views are not supported.
|
|
435
|
+
return undefined;
|
|
436
|
+
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* @deprecated Currently unused.
|
|
441
|
+
* Retrieves the index path of the cell that intersects the given point.
|
|
442
|
+
* The x position of the point is ignored; this method assumes that all rows have infinite width.
|
|
443
|
+
* If no cell intersects the point, this method will either return the previous
|
|
444
|
+
* or the next closest index path, depending on its direction parameter.
|
|
445
|
+
* @param point <BMPoint> The point.
|
|
446
|
+
* {
|
|
447
|
+
* @param directionNext <Boolean, nullable> Defaults to NO. If set to YES, this method will return
|
|
448
|
+
* the next closest index path to the given point if no cell intersects it;
|
|
449
|
+
* otherwise the previous closest index path is returned.
|
|
450
|
+
* }
|
|
451
|
+
* @return <BMIndexPath, nullable> The intersecting index path or undefined if the collection view contains no data.
|
|
452
|
+
*/
|
|
453
|
+
indexPathAtPoint: function (point, args) {
|
|
454
|
+
args = args || {};
|
|
455
|
+
var returnNext = args.directionNext;
|
|
456
|
+
|
|
457
|
+
var y = point.y;
|
|
458
|
+
|
|
459
|
+
// To determine the index path, the caret will continue to move through the index paths until it stops at the requested point.
|
|
460
|
+
var caret = 0;
|
|
461
|
+
|
|
462
|
+
var sectionPaddingTop = this._sectionInsets.top + this.headerHeight;
|
|
463
|
+
var sectionPaddingBottom = this._sectionInsets.bottom + this.footerHeight;
|
|
464
|
+
|
|
465
|
+
if (y <= sectionPaddingTop) return this.collectionView.indexPathForObjectAtRow(0, {inSectionAtIndex: 0});
|
|
466
|
+
|
|
467
|
+
caret = sectionPaddingTop;
|
|
468
|
+
|
|
469
|
+
var sectionCount = this.collectionView.numberOfSections();
|
|
470
|
+
|
|
471
|
+
if (this._rowHeight >= 0) {
|
|
472
|
+
for (var i = 0; i < sectionCount; i++) {
|
|
473
|
+
var objectCount = this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
// Check if the requested point is less than the point obtained by adding the heights of the rows in the current section
|
|
477
|
+
// If it is, the requested point will be on one of these rows
|
|
478
|
+
if (y <= caret + objectCount * this._rowHeight) {
|
|
479
|
+
// The caret intersects one of the rows
|
|
480
|
+
var distance = y - caret;
|
|
481
|
+
var row = Math.floor(distance / this._rowHeight) | 0;
|
|
482
|
+
|
|
483
|
+
return this.collectionView.indexPathForObjectAtRow(row, {inSectionAtIndex: i});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Otherwise advance the caret past these rows
|
|
487
|
+
caret += objectCount * this._rowHeight;
|
|
488
|
+
|
|
489
|
+
// If the requested point is less than the point obtained by adding the paddings between sections, the point falls between the current section
|
|
490
|
+
// and the next one
|
|
491
|
+
if (y <= caret + sectionPaddingBottom + sectionPaddingTop) {
|
|
492
|
+
// The caret doesn't intersect any row
|
|
493
|
+
|
|
494
|
+
if (returnNext) {
|
|
495
|
+
if (i < sectionCount - 1) {
|
|
496
|
+
// TODO: Handle empty sections
|
|
497
|
+
return this.collectionView.indexPathForObjectAtRow(0, {inSectionAtIndex: i + 1});
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
// TODO: Handle empty sections
|
|
501
|
+
// If this is the last section, there is no next item to return
|
|
502
|
+
return this.collectionView.indexPathForObjectAtRow(objectCount - 1, {inSectionAtIndex: i});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
// TODO: Handle empty sections
|
|
507
|
+
// If the previous item should be returned, it is the last item in the current section
|
|
508
|
+
return this.collectionView.indexPathForObjectAtRow(objectCount - 1, {inSectionAtIndex: i});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Otherwise advance the caret to the next section
|
|
513
|
+
caret += sectionPaddingBottom + sectionPaddingTop;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// TODO: Handle empty sections
|
|
517
|
+
// If none of the previous cases has returned any index path, the target point falls below all the content and the only closest item is the last one
|
|
518
|
+
return this.collectionView.indexPathForObjectAtRow(this.collectionView.numberOfObjectsInSectionAtIndex(sectionCount - 1), {inSectionAtIndex: sectionCount - 1});
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
for (var i = 0; i < sectionCount; i++) {
|
|
522
|
+
var objectCount = this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
523
|
+
|
|
524
|
+
// When dealing with variable height rows, each row must be treated separately
|
|
525
|
+
for (var j = 0; j < objectCount; j++) {
|
|
526
|
+
var rowHeight = this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(
|
|
527
|
+
this.collectionView,
|
|
528
|
+
this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i})
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
// The logic is similar to the one used for fixed-height rows, except that each row
|
|
532
|
+
// must be verified separately rather than in bulk.
|
|
533
|
+
if (y <= caret + rowHeight) {
|
|
534
|
+
// The point intesects this row
|
|
535
|
+
return this.collectionView.indexPathForObjectAtRow(j, {inSectionAtIndex: i});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Otherwise advance to the next one
|
|
539
|
+
caret += rowHeight;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// If the requested point is less than the point obtained by adding the paddings between sections, the point falls between the current section
|
|
543
|
+
// and the next one
|
|
544
|
+
if (y <= caret + sectionPaddingBottom + sectionPaddingTop) {
|
|
545
|
+
// The caret doesn't intersect any row
|
|
546
|
+
|
|
547
|
+
if (returnNext) {
|
|
548
|
+
if (i < sectionCount - 1) {
|
|
549
|
+
return this.collectionView.indexPathForObjectAtRow(0, {inSectionAtIndex: i + 1});
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
// If this is the last section, there is no next item to return
|
|
553
|
+
return this.collectionView.indexPathForObjectAtRow(objectCount - 1, {inSectionAtIndex: i});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
// If the previous item should be returned, it is the last item in the current section
|
|
558
|
+
return this.collectionView.indexPathForObjectAtRow(objectCount - 1, {inSectionAtIndex: i});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Otherwise advance the caret to the next section
|
|
563
|
+
caret += sectionPaddingBottom + sectionPaddingTop;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// If none of the previous cases has returned any index path, the target point falls below all the content and the only closest item is the last one
|
|
567
|
+
return this.collectionView.indexPathForObjectAtRow(this.collectionView.numberOfObjectsInSectionAtIndex(sectionCount - 1), {inSectionAtIndex: sectionCount - 1});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
},
|
|
571
|
+
|
|
572
|
+
// @override
|
|
573
|
+
attributesForElementsInRect: function (rect) {
|
|
574
|
+
|
|
575
|
+
var useOffset = this._cachedContentSize.height > this.collectionView.frame.size.height;
|
|
576
|
+
|
|
577
|
+
// When there are no sections in the collection view, this method will always return a single instance of attributes
|
|
578
|
+
// for the empty supplementary view
|
|
579
|
+
if (this.collectionView.numberOfSections() == 0) {
|
|
580
|
+
var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
581
|
+
BMCollectionViewTableLayoutSupplementaryView.Empty,
|
|
582
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([0])}
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
attribute.frame = BMRectMake(0, 0, this.collectionView.frame.size.width, this.collectionView.frame.size.height);
|
|
586
|
+
return [attribute];
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// The caret moves through the index paths until it reaches the end of the target rect
|
|
590
|
+
var caret = this._sectionInsets.top;
|
|
591
|
+
|
|
592
|
+
var hasVariableRowHeights = this._rowHeight < 0;
|
|
593
|
+
|
|
594
|
+
var sectionPaddingTop = this._sectionInsets.top + this.headerHeight;
|
|
595
|
+
var sectionPaddingBottom = this._sectionInsets.bottom + this.footerHeight;
|
|
596
|
+
|
|
597
|
+
var cellPositionX = this.sectionInsets.left;
|
|
598
|
+
var cellWidth = Math.max(this.collectionView.frame.size.width, this.minimumWidth + this.collectionView.scrollBarSize) - this.sectionInsets.left - this.sectionInsets.right - (useOffset ? this.collectionView.scrollBarSize : 0);
|
|
599
|
+
|
|
600
|
+
var topLimit = rect.origin.y;
|
|
601
|
+
var bottomLimit = rect.bottom;
|
|
602
|
+
|
|
603
|
+
var section = 0;
|
|
604
|
+
var row = -1;
|
|
605
|
+
|
|
606
|
+
// Skip the index paths that do not intersect this rect
|
|
607
|
+
while (true) {
|
|
608
|
+
// Check if the header intersects the rect
|
|
609
|
+
if (caret + this.headerHeight > topLimit) {
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Otherwise move the caret below the header
|
|
614
|
+
caret += this.headerHeight;
|
|
615
|
+
row = 0;
|
|
616
|
+
|
|
617
|
+
if (hasVariableRowHeights) {
|
|
618
|
+
var sectionObjectCount = this.collectionView.numberOfObjectsInSectionAtIndex(section);
|
|
619
|
+
|
|
620
|
+
for (var i = 0; i < sectionObjectCount; i++) {
|
|
621
|
+
var indexPath = this.collectionView.indexPathForObjectAtRow(i, {inSectionAtIndex: section});
|
|
622
|
+
var rowHeight = this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(this.collectionView, indexPath);
|
|
623
|
+
if (caret + rowHeight > topLimit) {
|
|
624
|
+
row = i;
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
caret += rowHeight;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
row = i;
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
var sectionHeight = this.collectionView.numberOfObjectsInSectionAtIndex(section) * this._rowHeight;
|
|
635
|
+
|
|
636
|
+
// Check if the contents intersect this rect
|
|
637
|
+
if (caret + sectionHeight > topLimit) {
|
|
638
|
+
var targetIndex = Math.floor((topLimit - caret) / this._rowHeight);
|
|
639
|
+
row = targetIndex;
|
|
640
|
+
|
|
641
|
+
caret += targetIndex * this._rowHeight;
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Otherwise move the caret to the footer
|
|
646
|
+
row = this.collectionView.numberOfObjectsInSectionAtIndex(section);
|
|
647
|
+
caret += sectionHeight;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Check if the footer intersects the rect
|
|
651
|
+
if (caret + this.footerHeight > topLimit) {
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Otherwise move on to the next section
|
|
656
|
+
caret += this.footerHeight + this.sectionInsets.top + this.sectionInsets.bottom;
|
|
657
|
+
section++;
|
|
658
|
+
row = -1;
|
|
659
|
+
|
|
660
|
+
// If the section index is out of bounds; there are no elements in the target rect
|
|
661
|
+
if (section >= this.collectionView.numberOfSections()) return [];
|
|
662
|
+
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// When the while loop exits, the caret is positioned at the first visible index path and attributes may be added
|
|
666
|
+
var attributes = [];
|
|
667
|
+
|
|
668
|
+
var topmostSection = section;
|
|
669
|
+
|
|
670
|
+
// If this layout pins headers to the content edge and the first index path is not this section's header, create its attributes
|
|
671
|
+
// now and add it to the attributes array
|
|
672
|
+
if (this._pinsHeadersToContentEdge && row > -1) {
|
|
673
|
+
var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
674
|
+
BMCollectionViewTableLayoutSupplementaryView.Header,
|
|
675
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([section])}
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
attribute.frame = BMRectMake(cellPositionX, this.collectionView.scrollOffset.y, cellWidth, this.headerHeight);
|
|
679
|
+
|
|
680
|
+
// Float the header above the content
|
|
681
|
+
attribute.style.zIndex = 1;
|
|
682
|
+
|
|
683
|
+
attributes.push(attribute);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
var bottommostSection = section;
|
|
687
|
+
var lastFooterAttributes, lastFooterAttributesSection;
|
|
688
|
+
|
|
689
|
+
// Continue adding attributes until the bottom of the rect is reached
|
|
690
|
+
while (caret < bottomLimit) {
|
|
691
|
+
|
|
692
|
+
// If the section index is out of bounds; there are no more elements in the target rect
|
|
693
|
+
if (section >= this.collectionView.numberOfSections()) break;
|
|
694
|
+
|
|
695
|
+
// Otherwise this might be the bottommost section
|
|
696
|
+
bottommostSection = section;
|
|
697
|
+
|
|
698
|
+
// The caret is at a header
|
|
699
|
+
if (row == -1) {
|
|
700
|
+
if (!this.showsHeaders) {
|
|
701
|
+
row = 0;
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
706
|
+
BMCollectionViewTableLayoutSupplementaryView.Header,
|
|
707
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([section])}
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
attribute.frame = BMRectMake(cellPositionX, caret, cellWidth, this.headerHeight);
|
|
711
|
+
|
|
712
|
+
// If the layout pins headers to content edges and this is the topmost section, adjust the header's attributes
|
|
713
|
+
if (this._pinsHeadersToContentEdge && section === topmostSection) {
|
|
714
|
+
|
|
715
|
+
// If the header's Y origin is lower than the content edge, the topmost section is an incoming section
|
|
716
|
+
// and the previous section's header should be rendered as outgoing
|
|
717
|
+
if (attribute.frame.origin.y > this.collectionView.scrollOffset.y) {
|
|
718
|
+
var previousSection = section - 1;
|
|
719
|
+
if (previousSection > -1) {
|
|
720
|
+
var previousHeaderAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
721
|
+
BMCollectionViewTableLayoutSupplementaryView.Header,
|
|
722
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([section - 1])}
|
|
723
|
+
);
|
|
724
|
+
previousHeaderAttributes.frame = BMRectMake(
|
|
725
|
+
cellPositionX,
|
|
726
|
+
Math.min(this.collectionView.scrollOffset.y, attribute.frame.origin.y - this.headerHeight),
|
|
727
|
+
cellWidth,
|
|
728
|
+
this.headerHeight
|
|
729
|
+
);
|
|
730
|
+
previousHeaderAttributes.style.zIndex = 1;
|
|
731
|
+
attributes.push(previousHeaderAttributes);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
// Header attributes can only be pinned downwards - this is to resolve the case where the first section has additional padding to the top edge.
|
|
737
|
+
// In that case, the header should remain visible at its regular position, rather than being shifted upwards.
|
|
738
|
+
attribute.frame.origin.y = Math.max(this.collectionView.scrollOffset.y, attribute.frame.origin.y);
|
|
739
|
+
|
|
740
|
+
// Float the header above the content
|
|
741
|
+
attribute.style.zIndex = 1;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
attributes.push(attribute);
|
|
745
|
+
|
|
746
|
+
caret += this.headerHeight;
|
|
747
|
+
row = 0;
|
|
748
|
+
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// The caret is at a footer
|
|
753
|
+
if (row == this.collectionView.numberOfObjectsInSectionAtIndex(section)) {
|
|
754
|
+
if (!this.showsFooters) {
|
|
755
|
+
row = -1;
|
|
756
|
+
section++;
|
|
757
|
+
caret += this.footerHeight + this.sectionInsets.top + this.sectionInsets.bottom;
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
762
|
+
BMCollectionViewTableLayoutSupplementaryView.Footer,
|
|
763
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([section])}
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
attribute.frame = BMRectMake(cellPositionX, caret, cellWidth, this.footerHeight);
|
|
767
|
+
attributes.push(attribute);
|
|
768
|
+
|
|
769
|
+
// Retain these attributes in case they have to be adjust for edge pinning
|
|
770
|
+
lastFooterAttributes = attribute;
|
|
771
|
+
lastFooterAttributesSection = section;
|
|
772
|
+
|
|
773
|
+
caret += this.footerHeight + this.sectionInsets.top + this.sectionInsets.bottom;
|
|
774
|
+
|
|
775
|
+
row = -1;
|
|
776
|
+
section++;
|
|
777
|
+
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// If the section index is out of bounds; there are no more elements left to add
|
|
782
|
+
if (section >= this.collectionView.numberOfSections()) return attributes;
|
|
783
|
+
|
|
784
|
+
// If the row doesn't match any of the other conditions, it is inside the section
|
|
785
|
+
var indexPath = this.collectionView.indexPathForObjectAtRow(row, {inSectionAtIndex: section});
|
|
786
|
+
var attribute = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(indexPath);
|
|
787
|
+
attribute.frame = BMRectMake(cellPositionX,
|
|
788
|
+
caret,
|
|
789
|
+
cellWidth,
|
|
790
|
+
hasVariableRowHeights ? this.collectionView.delegate.collectionViewRowHeightForCellAtIndexPath(this.collectionView, indexPath) : this._rowHeight
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
attributes.push(attribute);
|
|
794
|
+
caret += attribute.frame.size.height;
|
|
795
|
+
|
|
796
|
+
row++;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// If the footers are pinned to the content edge, create or retrieve the correct footer attributes for the bottommost section
|
|
800
|
+
// And adjust their position to the bottom edge of the collection view
|
|
801
|
+
if (this._pinsFootersToContentEdge) {
|
|
802
|
+
// Header attributes can only be pinned upwards - this is to resolve the case where the last section has additional padding to the bottom edge.
|
|
803
|
+
// In that case, the footer should remain visible at its regular position, rather than being shifted downwards.
|
|
804
|
+
if (bottommostSection === lastFooterAttributesSection) {
|
|
805
|
+
|
|
806
|
+
// If the footer's Y origin is higher than the content edge, the bottommost section is an outgoing section
|
|
807
|
+
// and the next section's footer should be rendered as incoming
|
|
808
|
+
if (lastFooterAttributes.frame.origin.y < this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - this.footerHeight) {
|
|
809
|
+
var nextSection = lastFooterAttributesSection + 1;
|
|
810
|
+
if (nextSection < this.collectionView.numberOfSections()) {
|
|
811
|
+
var nextFooterAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
812
|
+
BMCollectionViewTableLayoutSupplementaryView.Footer,
|
|
813
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([lastFooterAttributesSection + 1])}
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
nextFooterAttributes.frame = BMRectMake(
|
|
817
|
+
cellPositionX,
|
|
818
|
+
Math.max(this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - this.footerHeight, lastFooterAttributes.frame.bottom),
|
|
819
|
+
cellWidth,
|
|
820
|
+
this.footerHeight
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
attributes.push(nextFooterAttributes);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// If the footer attributes were already created, they just have to be adjusted
|
|
828
|
+
lastFooterAttributes.frame.origin.y = Math.min(lastFooterAttributes.frame.origin.y, this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - lastFooterAttributes.frame.size.height);
|
|
829
|
+
|
|
830
|
+
// Float the header above the content
|
|
831
|
+
lastFooterAttributes.style.zIndex = 1;
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
// If the attributes were not already created, they should be created now
|
|
835
|
+
var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
836
|
+
BMCollectionViewTableLayoutSupplementaryView.Footer,
|
|
837
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([bottommostSection])}
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
// If the attributes were not already created, then there is no chance they would appear above the bottom content edge, as they would normally
|
|
841
|
+
// appear below the currently visible area
|
|
842
|
+
attribute.frame = BMRectMake(cellPositionX, this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - this.footerHeight, cellWidth, this.footerHeight);
|
|
843
|
+
|
|
844
|
+
// Float the header above the content
|
|
845
|
+
attribute.style.zIndex = 1;
|
|
846
|
+
|
|
847
|
+
attributes.push(attribute);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return attributes;
|
|
852
|
+
|
|
853
|
+
},
|
|
854
|
+
|
|
855
|
+
// @override - BMCollectionViewLayout
|
|
856
|
+
initialAttributesForPresentedCellAtIndexPath: function (indexPath) {
|
|
857
|
+
var attributes = this.attributesForCellAtIndexPath(indexPath).copy();
|
|
858
|
+
|
|
859
|
+
if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForPresentedCellAtIndexPath) {
|
|
860
|
+
return this.collectionView.delegate.collectionViewInitialAttributesForPresentedCellAtIndexPath(this.collectionView, indexPath, {withTargetAttributes: attributes});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
attributes.frame.origin.x += attributes.frame.size.width / 2;
|
|
864
|
+
|
|
865
|
+
attributes.style = {
|
|
866
|
+
opacity: 0,
|
|
867
|
+
translateZ: 0
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
return attributes;
|
|
871
|
+
},
|
|
872
|
+
|
|
873
|
+
// @override - BMCollectionViewLayout
|
|
874
|
+
initialAttributesForPresentedSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
875
|
+
var attributes = this.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath}).copy();
|
|
876
|
+
|
|
877
|
+
if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForPresentedSupplementaryViewWithIdentifier) {
|
|
878
|
+
return this.collectionView.delegate.collectionViewInitialAttributesForPresentedSupplementaryViewWithIdentifier(this.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
attributes.frame.origin.x += attributes.frame.size.width / 2;
|
|
882
|
+
|
|
883
|
+
attributes.style = {
|
|
884
|
+
opacity: 0,
|
|
885
|
+
translateZ: 0
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
return attributes;
|
|
889
|
+
},
|
|
890
|
+
|
|
891
|
+
// @override
|
|
892
|
+
initialAttributesForAppearingCellAtIndexPath: function (indexPath) {
|
|
893
|
+
var attributes = this.attributesForCellAtIndexPath(indexPath).copy();
|
|
894
|
+
|
|
895
|
+
if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForAppearingCellAtIndexPath) {
|
|
896
|
+
return this.collectionView.delegate.collectionViewInitialAttributesForAppearingCellAtIndexPath(this.collectionView, indexPath, {withTargetAttributes: attributes});
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
attributes.frame.origin.x += attributes.frame.size.width / 2;
|
|
900
|
+
attributes.style = {opacity: 0, translateZ: 0};
|
|
901
|
+
|
|
902
|
+
return attributes;
|
|
903
|
+
|
|
904
|
+
},
|
|
905
|
+
|
|
906
|
+
// @override
|
|
907
|
+
initialAttributesForAppearingSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
908
|
+
var attributes = this.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath}).copy();
|
|
909
|
+
|
|
910
|
+
if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForAppearingSupplementaryViewWithIdentifier) {
|
|
911
|
+
return this.collectionView.delegate.collectionViewInitialAttributesForAppearingSupplementaryViewWithIdentifier(this.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
attributes.frame.origin.x += attributes.frame.size.width / 2;
|
|
915
|
+
attributes.style = {opacity: 0, translateZ: 0};
|
|
916
|
+
|
|
917
|
+
return attributes;
|
|
918
|
+
|
|
919
|
+
},
|
|
920
|
+
|
|
921
|
+
// @override
|
|
922
|
+
initialAttributesForMovingCellFromIndexPath: function (indexPath, options) {
|
|
923
|
+
var attributes;
|
|
924
|
+
var self = this;
|
|
925
|
+
this.collectionView.usingOldDataSet(function () {
|
|
926
|
+
attributes = self.attributesForCellAtIndexPath(indexPath);
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
return attributes;
|
|
930
|
+
},
|
|
931
|
+
|
|
932
|
+
// @override
|
|
933
|
+
initialAttributesForMovingSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
934
|
+
var attributes;
|
|
935
|
+
var self = this;
|
|
936
|
+
this.collectionView.usingOldDataSet(function () {
|
|
937
|
+
attributes = self.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath});
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
return attributes;
|
|
941
|
+
},
|
|
942
|
+
|
|
943
|
+
// @override
|
|
944
|
+
supplementaryViewsToInsert: function () {
|
|
945
|
+
var count = this.collectionView.numberOfSections();
|
|
946
|
+
var oldCount;
|
|
947
|
+
var self = this;
|
|
948
|
+
this.collectionView.usingOldDataSet(function () {
|
|
949
|
+
oldCount = self.collectionView.numberOfSections();
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
var views = [];
|
|
953
|
+
|
|
954
|
+
for (var i = oldCount; i < count; i++) {
|
|
955
|
+
if (this.showsHeaders) {
|
|
956
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
957
|
+
BMCollectionViewTableLayoutSupplementaryView.Header,
|
|
958
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
959
|
+
));
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (this.showsFooters) {
|
|
963
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
964
|
+
BMCollectionViewTableLayoutSupplementaryView.Footer,
|
|
965
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
966
|
+
));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
if (oldCount > 0 && count == 0) {
|
|
971
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
972
|
+
BMCollectionViewTableLayoutSupplementaryView.Empty,
|
|
973
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([0])}
|
|
974
|
+
));
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
return views;
|
|
978
|
+
},
|
|
979
|
+
|
|
980
|
+
// @override
|
|
981
|
+
supplementaryViewsToDelete: function () {
|
|
982
|
+
var count = this.collectionView.numberOfSections();
|
|
983
|
+
var oldCount;
|
|
984
|
+
var self = this;
|
|
985
|
+
this.collectionView.usingOldDataSet(function () {
|
|
986
|
+
oldCount = self.collectionView.numberOfSections();
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
var views = [];
|
|
990
|
+
|
|
991
|
+
for (var i = count; i < oldCount; i++) {
|
|
992
|
+
if (this.showsHeaders) {
|
|
993
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
994
|
+
BMCollectionViewTableLayoutSupplementaryView.Header,
|
|
995
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
996
|
+
));
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (this.showsFooters) {
|
|
1000
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
1001
|
+
BMCollectionViewTableLayoutSupplementaryView.Footer,
|
|
1002
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
1003
|
+
));
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
if (oldCount == 0 && count > 0) {
|
|
1008
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
1009
|
+
BMCollectionViewTableLayoutSupplementaryView.Empty,
|
|
1010
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([0])}
|
|
1011
|
+
));
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
return views;
|
|
1015
|
+
},
|
|
1016
|
+
|
|
1017
|
+
// @override
|
|
1018
|
+
finalAttributesForDisappearingCellAtIndexPath: function (indexPath) {
|
|
1019
|
+
var attributes;
|
|
1020
|
+
var self = this;
|
|
1021
|
+
|
|
1022
|
+
this.collectionView.usingOldDataSet(function () {
|
|
1023
|
+
attributes = self.attributesForCellAtIndexPath(indexPath).copy();
|
|
1024
|
+
|
|
1025
|
+
if (self.collectionView.delegate && self.collectionView.delegate.collectionViewFinalAttributesForDisappearingCellAtIndexPath) {
|
|
1026
|
+
attributes = self.collectionView.delegate.collectionViewFinalAttributesForDisappearingCellAtIndexPath(self.collectionView, indexPath, {withTargetAttributes: attributes});
|
|
1027
|
+
}
|
|
1028
|
+
else {
|
|
1029
|
+
attributes.frame.origin.x -= attributes.frame.size.width / 2;
|
|
1030
|
+
attributes.style = {opacity: 0, translateZ: 0};
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
return attributes;
|
|
1036
|
+
},
|
|
1037
|
+
|
|
1038
|
+
// @override
|
|
1039
|
+
finalAttributesForDisappearingSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
1040
|
+
var attributes;
|
|
1041
|
+
var self = this;
|
|
1042
|
+
|
|
1043
|
+
this.collectionView.usingOldDataSet(function () {
|
|
1044
|
+
attributes = self.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath}).copy();
|
|
1045
|
+
|
|
1046
|
+
if (self.collectionView.delegate && self.collectionView.delegate.collectionViewFinalAttributesForDisappearingSupplementaryViewWithIdentifier) {
|
|
1047
|
+
attributes = self.collectionView.delegate.collectionViewFinalAttributesForDisappearingSupplementaryViewWithIdentifier(self.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
attributes.frame.origin.x -= attributes.frame.size.width / 2;
|
|
1051
|
+
attributes.style = {opacity: 0, translateZ: 0};
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
return attributes;
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1059
|
+
// @override - BMCollectionViewLayout
|
|
1060
|
+
copy: function () {
|
|
1061
|
+
var copy = new BMCollectionViewTableLayout();
|
|
1062
|
+
|
|
1063
|
+
copy.rowHeight = this.rowHeight;
|
|
1064
|
+
|
|
1065
|
+
copy.showsHeaders = this.showsHeaders;
|
|
1066
|
+
copy.headerHeight = this.headerHeight;
|
|
1067
|
+
|
|
1068
|
+
copy.showsFooters = this.showsFooters;
|
|
1069
|
+
copy.footerHeight = this.footerHeight;
|
|
1070
|
+
|
|
1071
|
+
copy.sectionInsets = this.sectionInsets;
|
|
1072
|
+
|
|
1073
|
+
copy.pinsHeadersToContentEdge = this.pinsHeadersToContentEdge;
|
|
1074
|
+
copy.pinsFootersToContentEdge = this.pinsFootersToContentEdge;
|
|
1075
|
+
|
|
1076
|
+
return copy;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
// @endtype
|
|
1082
|
+
|
|
1083
|
+
// @type BMCollectionViewTreeLayout extends BMCollectionViewLayout
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* The BMCollectionViewTreeLayout will lay out its elements as a list where each row can have
|
|
1088
|
+
* either a fixed height or a variable height. Additionally, the elements may be nested, creating a tree-like view.
|
|
1089
|
+
*
|
|
1090
|
+
* The tree layout supports collapsible sections and it also supports collapsing the tree elements themselves.
|
|
1091
|
+
* Collection views that use the tree layout are required to have a delegate that implements the following methods:
|
|
1092
|
+
* String collectionViewLayoutTreeIdentifierForItemAtIndexPath(BMCollectionView, BMCollectionViewTreeLayout, BMIndexPath) - returns a unique identifier for each item in the collection view
|
|
1093
|
+
* nullable String collectionViewLayoutTreeParentIdentifierForItemAtIndexPath(BMCollectionView, BMCollectionViewTreeLayout, BMIndexPath) - returns the identifier of each item's parent, if it has one
|
|
1094
|
+
*
|
|
1095
|
+
* If the row heights are variable, the collection must have a delegate that implements the
|
|
1096
|
+
* Number collectionViewRowHeightForCellAtIndexPath(BMCollectionView, BMIndexPath) method.
|
|
1097
|
+
*
|
|
1098
|
+
* Optionally, the tree layout may also generate supplementary views for section headers, section footers and empty views.
|
|
1099
|
+
*/
|
|
1100
|
+
function BMCollectionViewTreeLayout() {} // <constructor>
|
|
1101
|
+
|
|
1102
|
+
BMExtend(BMCollectionViewTreeLayout.prototype, BMCollectionViewLayout.prototype, {
|
|
1103
|
+
|
|
1104
|
+
// @override - BMCollectionViewLayout
|
|
1105
|
+
get supportsCopying() {
|
|
1106
|
+
return YES;
|
|
1107
|
+
},
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* The row height to use for each row.
|
|
1111
|
+
* This can either be a specific size in pixels or BMCollectionViewTreeLayoutRowHeightVariable if each row can have a different height.
|
|
1112
|
+
* If the row heights are variable, the collection must have a delegate that implements the Number collectionViewRowHeightForCellAtIndexPath(BMCollectionView, BMIndexPath) method.
|
|
1113
|
+
*/
|
|
1114
|
+
_rowHeight: 44, // <Number>
|
|
1115
|
+
|
|
1116
|
+
get rowHeight() { return this._rowHeight; },
|
|
1117
|
+
set rowHeight(rowHeight) {
|
|
1118
|
+
this._rowHeight = rowHeight;
|
|
1119
|
+
this.invalidateLayout();
|
|
1120
|
+
},
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Determines whether or not the tree layout should generate header supplementary views or not.
|
|
1124
|
+
*/
|
|
1125
|
+
_showsHeaders: NO, // <Boolean>
|
|
1126
|
+
|
|
1127
|
+
get showsHeaders() { return this._showsHeaders; },
|
|
1128
|
+
set showsHeaders(showsHeaders) {
|
|
1129
|
+
this._showsHeaders = showsHeaders;
|
|
1130
|
+
this.invalidateLayout();
|
|
1131
|
+
},
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* The height to use for headers. Does not have any effect if showsHeaders is set to NO.
|
|
1135
|
+
* This attribute is always 0 if showsHeader is set to NO.
|
|
1136
|
+
*/
|
|
1137
|
+
_headerHeight: 22, // <Number>
|
|
1138
|
+
|
|
1139
|
+
get headerHeight() {
|
|
1140
|
+
return this._showsHeaders ? this._headerHeight : 0;
|
|
1141
|
+
},
|
|
1142
|
+
|
|
1143
|
+
set headerHeight(headerHeight) {
|
|
1144
|
+
this._headerHeight = headerHeight;
|
|
1145
|
+
if (this._showsHeader) this.invalidateLayout();
|
|
1146
|
+
},
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Determines whether or not the tree layout should generate footer supplementary views or not.
|
|
1150
|
+
*/
|
|
1151
|
+
_showsFooters: NO, // <Boolean>
|
|
1152
|
+
|
|
1153
|
+
get showsFooters() { return this._showsFooters; },
|
|
1154
|
+
set showsFooters(showsFooters) {
|
|
1155
|
+
this._showsFooters = showsFooters;
|
|
1156
|
+
this.invalidateLayout();
|
|
1157
|
+
},
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* The height to use for footers. Does not have any effect if showsFooters is set to NO.
|
|
1161
|
+
* This attribute is always 0 if showsFooters is set to NO.
|
|
1162
|
+
*/
|
|
1163
|
+
_footerHeight: 22, // <Number>
|
|
1164
|
+
|
|
1165
|
+
get footerHeight() {
|
|
1166
|
+
return this._showsFooters? this._footerHeight : 0;
|
|
1167
|
+
},
|
|
1168
|
+
|
|
1169
|
+
set footerHeight(footerHeight) {
|
|
1170
|
+
this._footerHeight = footerHeight;
|
|
1171
|
+
if (this._showsFooters) this.invalidateLayout();
|
|
1172
|
+
},
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* The amount by which child views will be indented.
|
|
1176
|
+
*/
|
|
1177
|
+
_indentWidth: 22, // <Number>
|
|
1178
|
+
|
|
1179
|
+
get indentWidth() {
|
|
1180
|
+
return this._indentWidth;
|
|
1181
|
+
},
|
|
1182
|
+
|
|
1183
|
+
set indentWidth(indentWidth) {
|
|
1184
|
+
this._indentWidth = indentWidth;
|
|
1185
|
+
this.invalidateLayout();
|
|
1186
|
+
},
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* The insets to apply to each section which control their distance from the collection view edges and from other sections.
|
|
1190
|
+
*/
|
|
1191
|
+
_sectionInsets: BMInsetMake(), // <BMInset, nullResettable>
|
|
1192
|
+
|
|
1193
|
+
get sectionInsets() { return this._sectionInsets; },
|
|
1194
|
+
set sectionInsets(insets) {
|
|
1195
|
+
this._sectionInsets = insets || BMInsetMake();
|
|
1196
|
+
this.invalidateLayout();
|
|
1197
|
+
},
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* When set to YES, the tree layout will stick the header supplementary views to the top edge of the collection view while scrolling.
|
|
1201
|
+
* Otherwise, the header supplementary views will scroll with the rest of the content.
|
|
1202
|
+
*/
|
|
1203
|
+
_pinsHeadersToContentEdge: NO, // <Boolean>
|
|
1204
|
+
|
|
1205
|
+
get pinsHeadersToContentEdge() { return this._pinsHeadersToContentEdge; },
|
|
1206
|
+
set pinsHeadersToContentEdge(pins) {
|
|
1207
|
+
this._pinsHeadersToContentEdge = pins;
|
|
1208
|
+
this.invalidateLayout();
|
|
1209
|
+
},
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* When set to YES, the tree layout will stick the footer supplementary views to the bottom edge of the collection view while scrolling.
|
|
1213
|
+
* Otherwise, the footer supplementary views will scroll with the rest of the content.
|
|
1214
|
+
*/
|
|
1215
|
+
_pinsFootersToContentEdge: NO, // <Boolean>
|
|
1216
|
+
|
|
1217
|
+
get pinsFootersToContentEdge() { return this._pinsFootersToContentEdge; },
|
|
1218
|
+
set pinsFootersToContentEdge(pins) {
|
|
1219
|
+
this._pinsFootersToContentEdge = pins;
|
|
1220
|
+
this.invalidateLayout();
|
|
1221
|
+
},
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Contains various cached layout attributes.
|
|
1226
|
+
*/
|
|
1227
|
+
_layoutCache: undefined, // <Object>
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
// @override - BMCollectionViewLayout
|
|
1231
|
+
prepareLayout: function () {
|
|
1232
|
+
// Tree layout supports invalidating with context, which allows for more efficient layout updates
|
|
1233
|
+
// by only the updating the actually changed content, rather than the entire layout cache
|
|
1234
|
+
var context = this.collectionView.invalidationContext;
|
|
1235
|
+
|
|
1236
|
+
// If there is no invalidation context, the entire layout cache has to be reconstructed
|
|
1237
|
+
if (!context) return this.rebuildLayout();
|
|
1238
|
+
},
|
|
1239
|
+
|
|
1240
|
+
// @override - BMCollectionViewLayout
|
|
1241
|
+
contentSize: function () {
|
|
1242
|
+
return this._layoutCache.contentSize;
|
|
1243
|
+
},
|
|
1244
|
+
|
|
1245
|
+
// @override - BMCollectionViewLayout
|
|
1246
|
+
shouldInvalidateLayoutForBoundsChange: function (bounds) {
|
|
1247
|
+
return this._pinsHeadersToContentEdge || this._pinsFootersToContentEdge;
|
|
1248
|
+
},
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Creates and returns a copy of this tree layout.
|
|
1252
|
+
* @return <BMCollectionViewTreeLayout> A tree layout.
|
|
1253
|
+
*/
|
|
1254
|
+
copy() {
|
|
1255
|
+
let layout = new BMCollectionViewTreeLayout();
|
|
1256
|
+
|
|
1257
|
+
layout._rowHeight = this._rowHeight;
|
|
1258
|
+
|
|
1259
|
+
layout._showsHeaders = this._showsHeaders;
|
|
1260
|
+
layout._headerHeight = this._headerHeight;
|
|
1261
|
+
layout._pinsHeadersToContentEdge = this._pinsHeadersToContentEdge;
|
|
1262
|
+
|
|
1263
|
+
layout._showsFooters = this._showsFooters;
|
|
1264
|
+
layout._footerHeight = this._footerHeight;
|
|
1265
|
+
layout._pinsFootersToContentEdge = this._pinsFootersToContentEdge;
|
|
1266
|
+
|
|
1267
|
+
layout._indentWidth = this._indentWidth;
|
|
1268
|
+
|
|
1269
|
+
layout._sectionInsets = this.sectionInsets.copy();
|
|
1270
|
+
|
|
1271
|
+
return layout;
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
// @endtype
|
|
1276
|
+
|
|
1277
|
+
// @type BMCollectionViewFlowLayoutGravity
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Controls how cells are distributed within their row.
|
|
1281
|
+
*/
|
|
1282
|
+
export var BMCollectionViewFlowLayoutGravity = Object.freeze({
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* The cells will flow to the center of the row with no spacing between them.
|
|
1286
|
+
*/
|
|
1287
|
+
Center: {},
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* The cells will be aligned to the start of the row.
|
|
1291
|
+
*/
|
|
1292
|
+
Start: {},
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* The cells will be aligned to the end of the row.
|
|
1296
|
+
*/
|
|
1297
|
+
End: {},
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* The cells will flow to the edges of the row with equal spacing between them.
|
|
1301
|
+
*/
|
|
1302
|
+
Edge: {},
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* The cells will flow such that they have equal spacing between them and the row edges.
|
|
1306
|
+
*/
|
|
1307
|
+
Spaced: {},
|
|
1308
|
+
|
|
1309
|
+
/**
|
|
1310
|
+
* The cells will flow such that they will have no spacing between them.
|
|
1311
|
+
* If the cells in a row do not occupy the entire horizontal alrea of that row, they will be expanded proportionally until they do.
|
|
1312
|
+
*/
|
|
1313
|
+
Expand: {}
|
|
1314
|
+
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
// @endtype
|
|
1318
|
+
|
|
1319
|
+
// @type BMCollectionViewFlowLayoutAlignment
|
|
1320
|
+
|
|
1321
|
+
export var BMCollectionViewFlowLayoutAlignment = (function () {
|
|
1322
|
+
const start = {};
|
|
1323
|
+
const end = {};
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* Controls how cells will be aligned within their row.
|
|
1327
|
+
*/
|
|
1328
|
+
var BMCollectionViewFlowLayoutAlignment = Object.freeze({
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* @deprecated Use `Start`
|
|
1332
|
+
* The cells will be aligned to the starting edge of the row.
|
|
1333
|
+
*/
|
|
1334
|
+
Top: start,
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* The cells will be aligned to the start of the row.
|
|
1338
|
+
*/
|
|
1339
|
+
Start: start,
|
|
1340
|
+
|
|
1341
|
+
/**
|
|
1342
|
+
* The cells will be aligned to the center of the row.
|
|
1343
|
+
*/
|
|
1344
|
+
Center: {},
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* @deprecated Use `End`
|
|
1348
|
+
* The cells will be aligned to the end of the row.
|
|
1349
|
+
*/
|
|
1350
|
+
Bottom: end,
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* The cells will be aligned to the end of the row.
|
|
1354
|
+
*/
|
|
1355
|
+
End: end,
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* The cells will expand to fit the entire height of the row.
|
|
1359
|
+
*/
|
|
1360
|
+
Expand: {}
|
|
1361
|
+
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
return BMCollectionViewFlowLayoutAlignment;
|
|
1365
|
+
})();
|
|
1366
|
+
|
|
1367
|
+
// @endtype
|
|
1368
|
+
|
|
1369
|
+
// @type BMCollectionViewFlowLayoutOrientation
|
|
1370
|
+
|
|
1371
|
+
/**
|
|
1372
|
+
* An enum containing the possible orientations that the flow layout can use.
|
|
1373
|
+
*/
|
|
1374
|
+
export var BMCollectionViewFlowLayoutOrientation = Object.freeze({
|
|
1375
|
+
/**
|
|
1376
|
+
* Indicates that the flow layout will arrange cells primarily along the horizontal axis.
|
|
1377
|
+
* When the orientation is set to this value the flow layout will scroll horizontally.
|
|
1378
|
+
*/
|
|
1379
|
+
Horizontal: {},
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* Indicates that the flow layout will arrange cells primarily along the vertical axis.
|
|
1383
|
+
* When the orientation is set to this value the tile flow will scroll vertically.
|
|
1384
|
+
*/
|
|
1385
|
+
Vertical: {}
|
|
1386
|
+
});
|
|
1387
|
+
|
|
1388
|
+
// @endtype
|
|
1389
|
+
|
|
1390
|
+
// @type BMCollectionViewFlowLayout extends BMCollectionViewLayout
|
|
1391
|
+
|
|
1392
|
+
/**
|
|
1393
|
+
* The identifiers for flow layout supplementary views.
|
|
1394
|
+
* As the flow layout only provides supplementary views for section headers and footers and empty data sets just like the table layout,
|
|
1395
|
+
* the identifiers used by the table layout are reused in this case.
|
|
1396
|
+
*/
|
|
1397
|
+
export var BMCollectionViewFlowLayoutSupplementaryView = BMCollectionViewTableLayoutSupplementaryView;
|
|
1398
|
+
|
|
1399
|
+
/**
|
|
1400
|
+
* The flow layout arranges cells in a horiztonally (TO DO) or vertically scrolling container.
|
|
1401
|
+
* The cells will each flow on a row until they no longer fit. After they extend past the horizontal margin, they will move on to the next row.
|
|
1402
|
+
*/
|
|
1403
|
+
export function BMCollectionViewFlowLayout() {}; // <constructor>
|
|
1404
|
+
|
|
1405
|
+
BMCollectionViewFlowLayout.prototype = BMExtend(Object.create(BMCollectionViewLayout.prototype), {
|
|
1406
|
+
|
|
1407
|
+
// @override - BMCollectionViewLayout
|
|
1408
|
+
get supportsCopying() {
|
|
1409
|
+
return YES;
|
|
1410
|
+
},
|
|
1411
|
+
|
|
1412
|
+
/**
|
|
1413
|
+
* The size of the cells, or undefined if the cells should be sized dynamically.
|
|
1414
|
+
* If the cells are sized dynamically, the collection view must have a delegate that implements the
|
|
1415
|
+
* BMSize collectionViewSizeForCellAtIndexPath(BMCollectionView, BMIndexPath) method,
|
|
1416
|
+
* otherwise the collection view will not be able to render any cells.
|
|
1417
|
+
*/
|
|
1418
|
+
_cellSize: undefined, // <BMSize, nullable>
|
|
1419
|
+
|
|
1420
|
+
get cellSize() { return this._cellSize; },
|
|
1421
|
+
set cellSize(size) {
|
|
1422
|
+
this._cellSize = size;
|
|
1423
|
+
|
|
1424
|
+
this.invalidateLayout();
|
|
1425
|
+
},
|
|
1426
|
+
|
|
1427
|
+
/**
|
|
1428
|
+
* When this property is set to a size, flow layout will use automatic cell sizing to measure and lay out
|
|
1429
|
+
* cells in its collection view.
|
|
1430
|
+
*
|
|
1431
|
+
* Because this will cause flow layout to continuously resize the collection view contents, the value of this property
|
|
1432
|
+
* should represent the size that most cells are expected to be in order to minimize the visual changes to the
|
|
1433
|
+
* scroll bar when cells are measured.
|
|
1434
|
+
*/
|
|
1435
|
+
_expectedCellSize: undefined, // <BMSize, nullable>
|
|
1436
|
+
|
|
1437
|
+
get expectedCellSize() {
|
|
1438
|
+
return this._expectedCellSize;
|
|
1439
|
+
},
|
|
1440
|
+
set expectedCellSize(cellSize) {
|
|
1441
|
+
this._expectedCellSize = cellSize;
|
|
1442
|
+
|
|
1443
|
+
this.invalidateLayout();
|
|
1444
|
+
},
|
|
1445
|
+
|
|
1446
|
+
/**
|
|
1447
|
+
* The spacing between each row. This is also used between the header and the first row and between the last row and the footer.
|
|
1448
|
+
*/
|
|
1449
|
+
_rowSpacing: 44, // <Number>
|
|
1450
|
+
get rowSpacing() { return this._rowSpacing; },
|
|
1451
|
+
set rowSpacing(spacing) {
|
|
1452
|
+
this._rowSpacing = spacing;
|
|
1453
|
+
|
|
1454
|
+
this.invalidateLayout();
|
|
1455
|
+
},
|
|
1456
|
+
|
|
1457
|
+
/**
|
|
1458
|
+
* Determines whether or not the table layout should generate header supplementary views or not.
|
|
1459
|
+
*/
|
|
1460
|
+
_showsHeaders: NO, // <Boolean>
|
|
1461
|
+
|
|
1462
|
+
get showsHeaders() { return this._showsHeaders; },
|
|
1463
|
+
set showsHeaders(showsHeaders) {
|
|
1464
|
+
this._showsHeaders = showsHeaders;
|
|
1465
|
+
this.invalidateLayout();
|
|
1466
|
+
},
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* The height to use for headers. Does not have any effect if showsHeaders is set to NO.
|
|
1470
|
+
* This attribute is always 0 if showsHeader is set to NO.
|
|
1471
|
+
*/
|
|
1472
|
+
_headerHeight: 22, // <Number>
|
|
1473
|
+
|
|
1474
|
+
get headerHeight() {
|
|
1475
|
+
return this._showsHeaders ? this._headerHeight : 0;
|
|
1476
|
+
},
|
|
1477
|
+
|
|
1478
|
+
set headerHeight(headerHeight) {
|
|
1479
|
+
this._headerHeight = headerHeight;
|
|
1480
|
+
if (this._showsHeader) this.invalidateLayout();
|
|
1481
|
+
},
|
|
1482
|
+
|
|
1483
|
+
/**
|
|
1484
|
+
* Determines whether or not the table layout should generate footer supplementary views or not.
|
|
1485
|
+
*/
|
|
1486
|
+
_showsFooters: NO, // <Boolean>
|
|
1487
|
+
|
|
1488
|
+
get showsFooters() { return this._showsFooters; },
|
|
1489
|
+
set showsFooters(showsFooters) {
|
|
1490
|
+
this._showsFooters = showsFooters;
|
|
1491
|
+
this.invalidateLayout();
|
|
1492
|
+
},
|
|
1493
|
+
|
|
1494
|
+
/**
|
|
1495
|
+
* The height to use for footers. Does not have any effect if showsFooters is set to NO.
|
|
1496
|
+
* This attribute is always 0 if showsFooters is set to NO.
|
|
1497
|
+
*/
|
|
1498
|
+
_footerHeight: 22, // <Number>
|
|
1499
|
+
|
|
1500
|
+
get footerHeight() {
|
|
1501
|
+
return this._showsFooters? this._footerHeight : 0;
|
|
1502
|
+
},
|
|
1503
|
+
|
|
1504
|
+
set footerHeight(footerHeight) {
|
|
1505
|
+
this._footerHeight = footerHeight;
|
|
1506
|
+
if (this._showsFooters) this.invalidateLayout();
|
|
1507
|
+
},
|
|
1508
|
+
|
|
1509
|
+
/**
|
|
1510
|
+
* The insets to apply to each section which control their distance from the collection view edges and from other sections.
|
|
1511
|
+
*/
|
|
1512
|
+
_sectionInsets: BMInsetMake(), // <BMInset, nullResettable>
|
|
1513
|
+
|
|
1514
|
+
get sectionInsets() { return this._sectionInsets; },
|
|
1515
|
+
set sectionInsets(insets) {
|
|
1516
|
+
this._sectionInsets = insets || BMInsetMake();
|
|
1517
|
+
this.invalidateLayout();
|
|
1518
|
+
},
|
|
1519
|
+
|
|
1520
|
+
/**
|
|
1521
|
+
* Controls how items are spaced in each row horizontally.
|
|
1522
|
+
*/
|
|
1523
|
+
_gravity: BMCollectionViewFlowLayoutGravity.Spaced, // <BMCollectionViewFlowLayoutGravity>
|
|
1524
|
+
get gravity() { return this._gravity; },
|
|
1525
|
+
set gravity(gravity) {
|
|
1526
|
+
this._gravity = gravity;
|
|
1527
|
+
|
|
1528
|
+
this.invalidateLayout();
|
|
1529
|
+
},
|
|
1530
|
+
|
|
1531
|
+
/**
|
|
1532
|
+
* Controls the minimum horizontal spacing between cells. This spacing is guaranteed to be applied between the cells
|
|
1533
|
+
* irrespective of the selected gravity.
|
|
1534
|
+
*/
|
|
1535
|
+
_minimumSpacing: 0, // <Number>
|
|
1536
|
+
get minimumSpacing() { return this._minimumSpacing; },
|
|
1537
|
+
set minimumSpacing(spacing) {
|
|
1538
|
+
this._minimumSpacing = spacing;
|
|
1539
|
+
|
|
1540
|
+
this.invalidateLayout();
|
|
1541
|
+
},
|
|
1542
|
+
|
|
1543
|
+
/**
|
|
1544
|
+
* When set to a positive number, this controls the maximum cells that are allowed to fit in each row.
|
|
1545
|
+
*/
|
|
1546
|
+
_maximumCellsPerRow: 0, // <Number>
|
|
1547
|
+
get maximumCellsPerRow() {
|
|
1548
|
+
return this._maximumCellsPerRow;
|
|
1549
|
+
},
|
|
1550
|
+
set maximumCellsPerRow(max) {
|
|
1551
|
+
if (max < 0) max = 0;
|
|
1552
|
+
this._maximumCellsPerRow = max;
|
|
1553
|
+
|
|
1554
|
+
this.invalidateLayout();
|
|
1555
|
+
},
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* Only used with fixed cell sizes. If set to YES, the final row in each section will be left-aligned.
|
|
1559
|
+
*/
|
|
1560
|
+
_leftAlignFinalRow: NO, // <Boolean>
|
|
1561
|
+
get leftAlignFinalRow() { return this._leftAlignFinalRow; },
|
|
1562
|
+
set leftAlignFinalRow(align) {
|
|
1563
|
+
this._leftAlignFinalRow = align;
|
|
1564
|
+
|
|
1565
|
+
this.invalidateLayout();
|
|
1566
|
+
},
|
|
1567
|
+
|
|
1568
|
+
/**
|
|
1569
|
+
* Controls how items are aligned in each row vertically.
|
|
1570
|
+
*/
|
|
1571
|
+
_alignment: BMCollectionViewFlowLayoutAlignment.Center, // <BMCollectionViewFlowLayoutAlignment>
|
|
1572
|
+
get alignment() { return this._alignment; },
|
|
1573
|
+
set alignment(alignment) {
|
|
1574
|
+
this._alignment = alignment;
|
|
1575
|
+
|
|
1576
|
+
this.invalidateLayout();
|
|
1577
|
+
},
|
|
1578
|
+
|
|
1579
|
+
/**
|
|
1580
|
+
* Controls the axis along which cells will be laid out primarily.
|
|
1581
|
+
*/
|
|
1582
|
+
_orientation: BMCollectionViewFlowLayoutOrientation.Vertical, // <BMCollectionViewFlowLayoutOrientation>
|
|
1583
|
+
get orientation() { return this._orientation; },
|
|
1584
|
+
set orientation(orientation) {
|
|
1585
|
+
this._orientation = orientation;
|
|
1586
|
+
|
|
1587
|
+
this.invalidateLayout();
|
|
1588
|
+
},
|
|
1589
|
+
|
|
1590
|
+
/**
|
|
1591
|
+
* The padding from the top edge to the first item.
|
|
1592
|
+
*/
|
|
1593
|
+
_topPadding: 0, // <Number>
|
|
1594
|
+
get topPadding() { return this._topPadding; },
|
|
1595
|
+
set topPadding(padding) {
|
|
1596
|
+
this._topPadding = padding;
|
|
1597
|
+
|
|
1598
|
+
this.invalidateLayout();
|
|
1599
|
+
},
|
|
1600
|
+
|
|
1601
|
+
/**
|
|
1602
|
+
* The padding from the bottom edge to the last item.
|
|
1603
|
+
*/
|
|
1604
|
+
_bottomPadding: 0, // <Number>
|
|
1605
|
+
get bottomPadding() { return this._bottomPadding; },
|
|
1606
|
+
set bottomPadding(padding) {
|
|
1607
|
+
this._bottomPadding = padding;
|
|
1608
|
+
|
|
1609
|
+
this.invalidateLayout();
|
|
1610
|
+
},
|
|
1611
|
+
|
|
1612
|
+
/**
|
|
1613
|
+
* Controls how the content is aligned when it is smaller than the collection view's frame.
|
|
1614
|
+
*/
|
|
1615
|
+
_contentGravity: BMCollectionViewFlowLayoutAlignment.Center, // <BMCollectionViewFlowLayoutAlignment>
|
|
1616
|
+
get contentGravity() { return this._contentGravity; },
|
|
1617
|
+
set contentGravity(gravity) {
|
|
1618
|
+
this._contentGravity = gravity;
|
|
1619
|
+
|
|
1620
|
+
this.invalidateLayout();
|
|
1621
|
+
},
|
|
1622
|
+
|
|
1623
|
+
/**
|
|
1624
|
+
* When set to YES, the flow layout will stick the header supplementary views to the top edge of the collection view while scrolling.
|
|
1625
|
+
* Otherwise, the header supplementary views will scroll with the rest of the content.
|
|
1626
|
+
*/
|
|
1627
|
+
_pinsHeadersToContentEdge: NO, // <Boolean>
|
|
1628
|
+
|
|
1629
|
+
get pinsHeadersToContentEdge() { return this._pinsHeadersToContentEdge; },
|
|
1630
|
+
set pinsHeadersToContentEdge(pins) {
|
|
1631
|
+
this._pinsHeadersToContentEdge = pins;
|
|
1632
|
+
this.invalidateLayout();
|
|
1633
|
+
},
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* When set to YES, the flow layout will stick the footer supplementary views to the bottom edge of the collection view while scrolling.
|
|
1637
|
+
* Otherwise, the footer supplementary views will scroll with the rest of the content.
|
|
1638
|
+
*/
|
|
1639
|
+
_pinsFootersToContentEdge: NO, // <Boolean>
|
|
1640
|
+
|
|
1641
|
+
get pinsFootersToContentEdge() { return this._pinsFootersToContentEdge; },
|
|
1642
|
+
set pinsFootersToContentEdge(pins) {
|
|
1643
|
+
this._pinsFootersToContentEdge = pins;
|
|
1644
|
+
this.invalidateLayout();
|
|
1645
|
+
},
|
|
1646
|
+
|
|
1647
|
+
|
|
1648
|
+
cachedLayout: undefined, // <Object>
|
|
1649
|
+
|
|
1650
|
+
// @override
|
|
1651
|
+
collectionViewWillStartUpdates: function (updates) {
|
|
1652
|
+
this.prepareLayout();
|
|
1653
|
+
|
|
1654
|
+
// When using automatic cell sizes, this layout preparation will only build a rough outline of the layout,
|
|
1655
|
+
// but because of how animated updates work in collection view, it is required to also complete the layout process
|
|
1656
|
+
// at least up to the current collection view bounds
|
|
1657
|
+
if (this._expectedCellSize) {
|
|
1658
|
+
this.cachedLayout.iterator.next({rect: this.collectionView.bounds});
|
|
1659
|
+
}
|
|
1660
|
+
},
|
|
1661
|
+
|
|
1662
|
+
// @override
|
|
1663
|
+
collectionViewDidStartUpdates: function () {
|
|
1664
|
+
this.previousLayout = undefined;
|
|
1665
|
+
},
|
|
1666
|
+
|
|
1667
|
+
// @override
|
|
1668
|
+
prepareLayout: function () {
|
|
1669
|
+
return this._prepareLayoutWithScrollbarOffset(NO);
|
|
1670
|
+
},
|
|
1671
|
+
|
|
1672
|
+
/**
|
|
1673
|
+
* Prepares the layout, optionally taking the scrollbar size into account.
|
|
1674
|
+
* @param useOffset <Boolean> When set to `YES` the layout will take the scrollbar size into account.
|
|
1675
|
+
*/
|
|
1676
|
+
_prepareLayoutWithScrollbarOffset: function (useOffset) {
|
|
1677
|
+
if (this.isChangingBounds) {
|
|
1678
|
+
// If the layout is invalidated because of scrolling, it is not required to recalculate the caches, so this request should be ignored.
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
let iterator = this._prepareLayoutWithScrollbarOffsetGenerator(useOffset);
|
|
1683
|
+
|
|
1684
|
+
// When not using automatic cell sizes, the generator function will run to finish from the first
|
|
1685
|
+
// invocation of next
|
|
1686
|
+
// When using automatic cell sizes, the first iteration will only build a rough outline of the expected layout
|
|
1687
|
+
// and must be retained so different portions of the layout can subsequently be computed
|
|
1688
|
+
let result = iterator.next();
|
|
1689
|
+
if (!result.done) {
|
|
1690
|
+
this._layoutIterator = iterator;
|
|
1691
|
+
this.cachedLayout.iterator = iterator;
|
|
1692
|
+
}
|
|
1693
|
+
},
|
|
1694
|
+
|
|
1695
|
+
/**
|
|
1696
|
+
* A generator that iterates through creating the layout. With the exception of using automatic cell sizes,
|
|
1697
|
+
* this generator will execute as a single contiguous function.
|
|
1698
|
+
*
|
|
1699
|
+
* When using automatic cell sizes, the generator will yield execution and only calculate portions of the layout on demand.
|
|
1700
|
+
* With the exeption of the first invocation, it is required to pass an index path to the iterator's `next` method. The layout process
|
|
1701
|
+
* will continue until it fully lays out the cell for that index path, at which point it will yield again.
|
|
1702
|
+
* @param useOffset <Boolean> When set to `YES` the layout will take the scrollbar size into account.
|
|
1703
|
+
*/
|
|
1704
|
+
*_prepareLayoutWithScrollbarOffsetGenerator(useOffset) {
|
|
1705
|
+
|
|
1706
|
+
// To standardize naming in order to support the orientation setting, the following terms are used:
|
|
1707
|
+
// Length - refers to the dimension across the axis on which collection view scrolls: height for vertical and width for horizontal
|
|
1708
|
+
// Breadth - refers to the dimension across the secondary axis: width for vertical and height for horizontal
|
|
1709
|
+
// Size - refers to the dimension of cells across the axis on which collection view scrolls: height for vertical and width for horizontal
|
|
1710
|
+
// Secondary Size - refers to the dimension of cells across the secondary axis: width for vertical and height for horizontal
|
|
1711
|
+
|
|
1712
|
+
if (this.isChangingBounds) {
|
|
1713
|
+
// If the layout is invalidated because of scrolling, it is not required to recalculate the caches, so this request should be ignored.
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// If useOffset is set to YES, this the second layout pass and the previous layout should no longer be changed
|
|
1718
|
+
this.previousLayout = useOffset ? this.previousLayout : this.cachedLayout;
|
|
1719
|
+
|
|
1720
|
+
var frameHeight = this.collectionView.frame.size.height;
|
|
1721
|
+
var frameWidth = this.collectionView.frame.size.width;
|
|
1722
|
+
let frameSize = this._orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? frameHeight : frameWidth;
|
|
1723
|
+
|
|
1724
|
+
if (!this._cellSize || this._expectedCellSize) {
|
|
1725
|
+
// When using custom cell sizes, the layout will pre-compute the entire layout attributes
|
|
1726
|
+
this.cachedLayout = { sections: [] };
|
|
1727
|
+
|
|
1728
|
+
/*
|
|
1729
|
+
declare interface BMCollectionViewFlowLayoutCache {
|
|
1730
|
+
availableWidth: number;
|
|
1731
|
+
availableHeight: number;
|
|
1732
|
+
resolvedWidth?: number;
|
|
1733
|
+
resolvedHeight?: number;
|
|
1734
|
+
resolvedIndexPath?: BMIndexPath;
|
|
1735
|
+
sections: {
|
|
1736
|
+
top: number;
|
|
1737
|
+
bottom: number;
|
|
1738
|
+
rows: {
|
|
1739
|
+
top: number;
|
|
1740
|
+
bottom: number;
|
|
1741
|
+
startIndex: number;
|
|
1742
|
+
endIndex: number;
|
|
1743
|
+
attributes: BMCollectionViewLayoutAttributes[];
|
|
1744
|
+
}[];
|
|
1745
|
+
}[];
|
|
1746
|
+
size: BMSize;
|
|
1747
|
+
}
|
|
1748
|
+
*/
|
|
1749
|
+
|
|
1750
|
+
var cachedLayout = this.cachedLayout;
|
|
1751
|
+
|
|
1752
|
+
cachedLayout.pinsHeadersToContentEdge = this._pinsHeadersToContentEdge;
|
|
1753
|
+
cachedLayout.pinsFootersToContentEdge = this._pinsFootersToContentEdge;
|
|
1754
|
+
|
|
1755
|
+
let orientation = this._orientation;
|
|
1756
|
+
cachedLayout.orientation = orientation;
|
|
1757
|
+
|
|
1758
|
+
// Each section adds height for its insets and headers and footers
|
|
1759
|
+
var insetTop = this._sectionInsets.top;
|
|
1760
|
+
var insetBottom = this._sectionInsets.bottom;
|
|
1761
|
+
var insetLeft = this._sectionInsets.left;
|
|
1762
|
+
var insetRight = this._sectionInsets.right;
|
|
1763
|
+
|
|
1764
|
+
var topPadding = this._topPadding;
|
|
1765
|
+
var bottomPadding = this._bottomPadding;
|
|
1766
|
+
|
|
1767
|
+
var headerHeight = this.headerHeight;
|
|
1768
|
+
var footerHeight = this.footerHeight;
|
|
1769
|
+
|
|
1770
|
+
var left = this._sectionInsets.left;
|
|
1771
|
+
let right = this._sectionInsets.right;
|
|
1772
|
+
|
|
1773
|
+
// The available height is the collection view frame height minus the vertical insets and the scrollbar width
|
|
1774
|
+
// When headers, footers are used their size and the row spacing is included as well
|
|
1775
|
+
var height = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? topPadding :
|
|
1776
|
+
this.collectionView.frame.size.height - this._sectionInsets.top - this._sectionInsets.bottom - (useOffset ? this.collectionView.scrollBarSize : 0) - topPadding - bottomPadding;
|
|
1777
|
+
cachedLayout.availableHeight = height;
|
|
1778
|
+
|
|
1779
|
+
// The available width is the collection view frame width minus the horizontal insets and the scrollbar width
|
|
1780
|
+
var width = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ?
|
|
1781
|
+
this.collectionView.frame.size.width - this._sectionInsets.left - this._sectionInsets.right - (useOffset ? this.collectionView.scrollBarSize : 0) :
|
|
1782
|
+
left;
|
|
1783
|
+
cachedLayout.availableWidth = width;
|
|
1784
|
+
|
|
1785
|
+
var showsHeaders = this.showsHeaders;
|
|
1786
|
+
var showsFooters = this.showsFooters;
|
|
1787
|
+
|
|
1788
|
+
var headerHeight = this.headerHeight;
|
|
1789
|
+
var footerHeight = this.footerHeight;
|
|
1790
|
+
|
|
1791
|
+
var sectionCount = this.collectionView.numberOfSections();
|
|
1792
|
+
|
|
1793
|
+
var rowSpacing = this._rowSpacing;
|
|
1794
|
+
|
|
1795
|
+
var minimumSpacing = this._minimumSpacing;
|
|
1796
|
+
|
|
1797
|
+
// When using automatic cell sizes, first compute a preliminary size for the layout then yield,
|
|
1798
|
+
// awaiting for collection view to request attributes for cells at which point execution should
|
|
1799
|
+
// resume to compute the layout for those attributes
|
|
1800
|
+
/*
|
|
1801
|
+
declare interface BMCollectionViewAutomaticCellSizeTarget {
|
|
1802
|
+
targetIndexPath?: BMIndexPath;
|
|
1803
|
+
targetRect?: BMRect;
|
|
1804
|
+
}
|
|
1805
|
+
*/
|
|
1806
|
+
let target;
|
|
1807
|
+
|
|
1808
|
+
// The sections that contain the preliminary layout
|
|
1809
|
+
const preliminarySections = [];
|
|
1810
|
+
|
|
1811
|
+
const self = this;
|
|
1812
|
+
|
|
1813
|
+
// This function measures a series of cells that exist in the preliminary layout and interect the given rect
|
|
1814
|
+
function measureCellsInRect(rect) {
|
|
1815
|
+
// Adjust the rows to account for the resolved layout
|
|
1816
|
+
if (cachedLayout.resolvedIndexPath) {
|
|
1817
|
+
let targetSection = cachedLayout.resolvedIndexPath.section;
|
|
1818
|
+
if (cachedLayout.resolvedIndexPath.row == -1) targetSection--;
|
|
1819
|
+
let adjustment;
|
|
1820
|
+
for (let i = 0; i <= targetSection; i++) {
|
|
1821
|
+
let preliminarySection = preliminarySections[i];
|
|
1822
|
+
let section = cachedLayout.sections[i];
|
|
1823
|
+
|
|
1824
|
+
if (i < targetSection || section.resolved) {
|
|
1825
|
+
// Completely resolved sections can just be replaced entirely
|
|
1826
|
+
preliminarySection.start = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? section.top : section.left;
|
|
1827
|
+
preliminarySection.preliminaryEnd = preliminarySection.end;
|
|
1828
|
+
preliminarySection.end = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? section.bottom : section.right;
|
|
1829
|
+
|
|
1830
|
+
preliminarySection.preliminaryRows = preliminarySection.rows;
|
|
1831
|
+
preliminarySection.rows = section.rows.map(r => orientation == BMCollectionViewFlowLayoutOrientation.Vertical ?
|
|
1832
|
+
{start: r.top, end: r.bottom, indexStart: r.startIndex, indexEnd: r.endIndex} :
|
|
1833
|
+
{start: r.left, end: r.right, indexStart: r.startIndex, indexEnd: r.endIndex});
|
|
1834
|
+
}
|
|
1835
|
+
else {
|
|
1836
|
+
preliminarySection.preliminaryRows = preliminarySection.rows;
|
|
1837
|
+
preliminarySection.rows = [];
|
|
1838
|
+
preliminarySection.start = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? section.top : section.left;
|
|
1839
|
+
// For in-progress sections, it is necessary to figure out the adjustment between the
|
|
1840
|
+
// preliminary locations and the resolved ones
|
|
1841
|
+
let end = preliminarySection.start;
|
|
1842
|
+
let indexEnd = 0;
|
|
1843
|
+
for (const r of section.rows) {
|
|
1844
|
+
let row = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ?
|
|
1845
|
+
{start: r.top, end: r.bottom, indexStart: r.startIndex, indexEnd: r.endIndex} :
|
|
1846
|
+
{start: r.left, end: r.right, indexStart: r.startIndex, indexEnd: r.endIndex};
|
|
1847
|
+
|
|
1848
|
+
preliminarySection.rows.push(row);
|
|
1849
|
+
end = row.end;
|
|
1850
|
+
indexEnd = row.indexEnd;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
end += rowSpacing;
|
|
1854
|
+
|
|
1855
|
+
// After inspecting the resolved rows, it is possible to calculate the adjustment
|
|
1856
|
+
for (const row of preliminarySection.preliminaryRows) {
|
|
1857
|
+
if (row.indexEnd > indexEnd) {
|
|
1858
|
+
adjustment = end - row.start;
|
|
1859
|
+
break;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
// The adjustment can then be applied to all subsequent rows
|
|
1864
|
+
for (const row of preliminarySection.preliminaryRows) {
|
|
1865
|
+
if (row.indexEnd > indexEnd) {
|
|
1866
|
+
row.start += adjustment;
|
|
1867
|
+
row.end += adjustment;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// The remaining sections also need to be adjusted
|
|
1874
|
+
for (let i = targetSection + 1; i < sectionCount; i++) {
|
|
1875
|
+
// If the adjustment is still undefined, the target section was fully defined; in this case
|
|
1876
|
+
// the adjustment can be calculated directly from the section ends
|
|
1877
|
+
if (adjustment === undefined) {
|
|
1878
|
+
adjustment = (orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? cachedLayout.sections[targetSection].bottom : cachedLayout.sections[targetSection].right) - preliminarySections[targetSection].end;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
const preliminarySection = preliminarySections[i];
|
|
1882
|
+
preliminarySection.start += adjustment;
|
|
1883
|
+
preliminarySection.end += adjustment;
|
|
1884
|
+
|
|
1885
|
+
for (const row of preliminarySection.row) {
|
|
1886
|
+
row.start += adjustment;
|
|
1887
|
+
row.end += adjustment;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
let indexPaths = [];
|
|
1893
|
+
|
|
1894
|
+
var rectTop = rect.origin.y;
|
|
1895
|
+
var rectBottom = rect.bottom;
|
|
1896
|
+
var rectLeft = rect.origin.x;
|
|
1897
|
+
var rectRight = rect.right;
|
|
1898
|
+
var rectEnd = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? rectBottom : rectRight;
|
|
1899
|
+
const rectStart = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? rectTop : rectLeft;
|
|
1900
|
+
|
|
1901
|
+
var sectionIndex = 0;
|
|
1902
|
+
var sectionCount = preliminarySections.length;
|
|
1903
|
+
var section;
|
|
1904
|
+
|
|
1905
|
+
// Find the section that intersects this rect's top origin
|
|
1906
|
+
for (; sectionIndex < sectionCount; sectionIndex++) {
|
|
1907
|
+
section = preliminarySections[sectionIndex];
|
|
1908
|
+
if (section.end > rectStart) break;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
// If there were no sections return nothing
|
|
1912
|
+
if (sectionIndex == sectionCount) return;
|
|
1913
|
+
|
|
1914
|
+
// Find the first row index that intersects the rect's top.
|
|
1915
|
+
var rowIndex = 0;
|
|
1916
|
+
|
|
1917
|
+
for (const row of section.rows) {
|
|
1918
|
+
if (row.end > rectStart) break;
|
|
1919
|
+
rowIndex++;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
// Then continue adding rows until reaching the end of the rect or the end of the data set
|
|
1923
|
+
while (YES) {
|
|
1924
|
+
section = preliminarySections[sectionIndex];
|
|
1925
|
+
if (!section) return self.collectionView.measureSizesOfCellsAtIndexPaths(indexPaths);
|
|
1926
|
+
const rowCount = section.rows.length;
|
|
1927
|
+
for (let i = rowIndex; i < rowCount; i++) {
|
|
1928
|
+
const row = section.rows[i];
|
|
1929
|
+
// When encountering a row outside of the rect's bounds, perform the measurement and return
|
|
1930
|
+
if (row.start > rectEnd) return self.collectionView.measureSizesOfCellsAtIndexPaths(indexPaths);
|
|
1931
|
+
const indexEnd = Math.min(row.indexEnd + 1, self.collectionView.numberOfObjectsInSectionAtIndex(sectionIndex));
|
|
1932
|
+
for (let rowIndex = row.indexStart; rowIndex < indexEnd; rowIndex++) {
|
|
1933
|
+
indexPaths.push(self.collectionView.indexPathForObjectAtRow(rowIndex, {inSectionAtIndex: sectionIndex}));
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
sectionIndex++;
|
|
1938
|
+
rowIndex = 0;
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1941
|
+
|
|
1942
|
+
if (this._expectedCellSize) {
|
|
1943
|
+
cachedLayout.resolvedHeight = 0;
|
|
1944
|
+
cachedLayout.resolvedWidth = 0;
|
|
1945
|
+
cachedLayout.resolvedIndexPath = BMIndexPathMakeWithRow(-1, {inSectionAtIndex: -1});
|
|
1946
|
+
cachedLayout.sectionCount = this.collectionView.numberOfSections();
|
|
1947
|
+
|
|
1948
|
+
let numberOfColumns = this._maximumCellsPerRow;
|
|
1949
|
+
let expectedWidth = this._expectedCellSize.width;
|
|
1950
|
+
let expectedHeight = this._expectedCellSize.height;
|
|
1951
|
+
let expectedSize = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? expectedWidth : expectedHeight;
|
|
1952
|
+
let expectedSecondarySize = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? expectedHeight : expectedWidth;
|
|
1953
|
+
let maximumLength = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? width : height;
|
|
1954
|
+
if (!numberOfColumns) {
|
|
1955
|
+
if (cachedLayout.minimumSpacing) {
|
|
1956
|
+
// Otherwise it has to be computed
|
|
1957
|
+
numberOfColumns = 0;
|
|
1958
|
+
let usedLength = 0;
|
|
1959
|
+
|
|
1960
|
+
// For spaced gravity, the used width starts with at least two minimum spacings
|
|
1961
|
+
if (this._gravity === BMCollectionViewFlowLayoutGravity.Spaced) {
|
|
1962
|
+
usedLength = 2 * minimumSpacing;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
do {
|
|
1966
|
+
// If this cell no longer fits within the row then the column limit has been reached
|
|
1967
|
+
if (usedLength && usedLength + expectedSize + minimumSpacing > maximumLength) {
|
|
1968
|
+
break;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// For every cell other than the first, also include the required minimum spacing
|
|
1972
|
+
if (numberOfColumns) {
|
|
1973
|
+
usedLength += minimumSpacing;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
usedLength += expectedSize;
|
|
1977
|
+
|
|
1978
|
+
numberOfColumns++;
|
|
1979
|
+
} while (usedLength < maximumLength && expectedSize);
|
|
1980
|
+
}
|
|
1981
|
+
else {
|
|
1982
|
+
// If there is no minimum spacing required, then the number of columns is simply the available space divided
|
|
1983
|
+
// by the cell width
|
|
1984
|
+
numberOfColumns = ((maximumLength / expectedSize) | 0) || 1;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
// Create the preliminary sections
|
|
1989
|
+
for (let i = 0; i < sectionCount; i++) {
|
|
1990
|
+
let numberOfObjects = this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
1991
|
+
|
|
1992
|
+
let numberOfRows = Math.ceil(numberOfObjects / numberOfColumns) | 0;
|
|
1993
|
+
|
|
1994
|
+
let spacingBreadth = (numberOfRows - 1) * rowSpacing;
|
|
1995
|
+
if (headerHeight) spacingBreadth += rowSpacing;
|
|
1996
|
+
if (footerHeight) spacingBreadth += rowSpacing;
|
|
1997
|
+
|
|
1998
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
1999
|
+
// Section objects retain the overall bounds as well as the preliminary rows
|
|
2000
|
+
let section = {
|
|
2001
|
+
top: height,
|
|
2002
|
+
start: height,
|
|
2003
|
+
bottom: height + footerHeight + headerHeight + (expectedSecondarySize * numberOfRows) + spacingBreadth,
|
|
2004
|
+
end: height + footerHeight + headerHeight + (expectedSecondarySize * numberOfRows) + spacingBreadth,
|
|
2005
|
+
numberOfObjects: numberOfObjects,
|
|
2006
|
+
numberOfRows: numberOfRows,
|
|
2007
|
+
rows: []
|
|
2008
|
+
};
|
|
2009
|
+
|
|
2010
|
+
let rowStart = headerHeight ? height + headerHeight + rowSpacing : height;
|
|
2011
|
+
let indexStart = 0;
|
|
2012
|
+
for (let i = 0; i < numberOfRows; i++) {
|
|
2013
|
+
// Preliminary rows contain the overall bounds and starting and ending index paths
|
|
2014
|
+
section.rows.push({start: rowStart, end: rowStart + expectedSecondarySize, indexStart: indexStart, indexEnd: indexStart + numberOfColumns - 1});
|
|
2015
|
+
rowStart += expectedSecondarySize + rowSpacing;
|
|
2016
|
+
indexStart += numberOfColumns;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
height = section.bottom + insetTop + insetBottom;
|
|
2020
|
+
|
|
2021
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2022
|
+
if (height > frameHeight) {
|
|
2023
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
preliminarySections.push(section);
|
|
2027
|
+
}
|
|
2028
|
+
else {
|
|
2029
|
+
let section = {
|
|
2030
|
+
left: width,
|
|
2031
|
+
start: width,
|
|
2032
|
+
right: width + (expectedSecondarySize * numberOfRows) + spacingBreadth,
|
|
2033
|
+
end: width + (expectedSecondarySize * numberOfRows) + spacingBreadth,
|
|
2034
|
+
numberOfObjects: numberOfObjects,
|
|
2035
|
+
numberOfRows: numberOfRows,
|
|
2036
|
+
rows: []
|
|
2037
|
+
};
|
|
2038
|
+
|
|
2039
|
+
let rowStart = width;
|
|
2040
|
+
let indexStart = 0;
|
|
2041
|
+
for (let i = 0; i < numberOfRows; i++) {
|
|
2042
|
+
// Preliminary rows contain the overall bounds and starting and ending index paths
|
|
2043
|
+
section.rows.push({start: rowStart, end: rowStart + expectedSecondarySize, indexStart: indexStart, indexEnd: indexStart + numberOfColumns - 1});
|
|
2044
|
+
rowStart += expectedSecondarySize + rowSpacing;
|
|
2045
|
+
indexStart += numberOfColumns;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
width = section.right + insetLeft + insetRight;
|
|
2049
|
+
|
|
2050
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2051
|
+
if (width > frameWidth) {
|
|
2052
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
preliminarySections.push(section);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2060
|
+
height += this._bottomPadding;
|
|
2061
|
+
|
|
2062
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2063
|
+
if (height > frameHeight) {
|
|
2064
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
cachedLayout.size = BMSizeMake(this.collectionView.frame.size.width, height);
|
|
2068
|
+
|
|
2069
|
+
target = yield;
|
|
2070
|
+
|
|
2071
|
+
// When requesting a rect initially, pre-measure the cells up to that rect
|
|
2072
|
+
if (target.rect) {
|
|
2073
|
+
measureCellsInRect(target.rect);
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
height = topPadding;
|
|
2077
|
+
}
|
|
2078
|
+
else {
|
|
2079
|
+
if (width > frameWidth) {
|
|
2080
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
cachedLayout.size = BMSizeMake(width, this.collectionView.frame.size.height);
|
|
2084
|
+
|
|
2085
|
+
target = yield;
|
|
2086
|
+
// When requesting a rect initially, pre-measure the cells up to that rect
|
|
2087
|
+
if (target.rect) {
|
|
2088
|
+
measureCellsInRect(target.rect);
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
width = 0;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// Go through the sections and create the layout attributes for all objects
|
|
2096
|
+
for (var i = 0; i < sectionCount; i++) {
|
|
2097
|
+
|
|
2098
|
+
// The section is plain old javascript object containing various cached attributes
|
|
2099
|
+
// And a rows array. Each item in a rows array is another object that contains
|
|
2100
|
+
// cached properties for that row and all the attributes of items in that row.
|
|
2101
|
+
var section = {
|
|
2102
|
+
rows: []
|
|
2103
|
+
};
|
|
2104
|
+
|
|
2105
|
+
// Save the cached section
|
|
2106
|
+
cachedLayout.sections.push(section);
|
|
2107
|
+
|
|
2108
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2109
|
+
height += insetTop;
|
|
2110
|
+
// Cache the top origin
|
|
2111
|
+
section.top = height;
|
|
2112
|
+
}
|
|
2113
|
+
else {
|
|
2114
|
+
width += insetLeft;
|
|
2115
|
+
// Cache the top origin
|
|
2116
|
+
section.left = width;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
if (showsHeaders) {
|
|
2120
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2121
|
+
// Cache the header attributes if they are enabled
|
|
2122
|
+
var headerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
2123
|
+
BMCollectionViewFlowLayoutSupplementaryView.Header,
|
|
2124
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
2125
|
+
);
|
|
2126
|
+
|
|
2127
|
+
headerAttributes.frame = BMRectMake(left, height, width, headerHeight);
|
|
2128
|
+
if (this.pinsHeadersToContentEdge) headerAttributes.style = {zIndex: 1};
|
|
2129
|
+
|
|
2130
|
+
section.headerAttributes = headerAttributes;
|
|
2131
|
+
|
|
2132
|
+
height += headerHeight + rowSpacing;
|
|
2133
|
+
}
|
|
2134
|
+
else {
|
|
2135
|
+
// Cache the header attributes if they are enabled
|
|
2136
|
+
var headerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
2137
|
+
BMCollectionViewFlowLayoutSupplementaryView.Header,
|
|
2138
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
2139
|
+
);
|
|
2140
|
+
|
|
2141
|
+
headerAttributes.frame = BMRectMake(width, topPadding + insetTop, headerHeight, height);
|
|
2142
|
+
if (this.pinsHeadersToContentEdge) headerAttributes.style = {zIndex: 1};
|
|
2143
|
+
|
|
2144
|
+
section.headerAttributes = headerAttributes;
|
|
2145
|
+
|
|
2146
|
+
width += headerHeight + rowSpacing;
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
var sectionItems = this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
2151
|
+
var sectionIndex = 0;
|
|
2152
|
+
|
|
2153
|
+
let maximumRowLength = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? width : height;
|
|
2154
|
+
let lengthAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'width' : 'height';
|
|
2155
|
+
let breadthAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'height' : 'width';
|
|
2156
|
+
let rowStartAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'top' : 'left';
|
|
2157
|
+
|
|
2158
|
+
// Cache the cells in this sections
|
|
2159
|
+
while (sectionIndex < sectionItems) {
|
|
2160
|
+
|
|
2161
|
+
// Construct new rows until there are no more items in this sections
|
|
2162
|
+
var row = {
|
|
2163
|
+
top: height,
|
|
2164
|
+
left: width,
|
|
2165
|
+
attributes: [],
|
|
2166
|
+
startIndex: sectionIndex
|
|
2167
|
+
};
|
|
2168
|
+
|
|
2169
|
+
var rowLength = 0;
|
|
2170
|
+
var rowBreadth = 0;
|
|
2171
|
+
|
|
2172
|
+
// For spaced gravity, the used width starts with at least two minimum spacings
|
|
2173
|
+
if (this._gravity === BMCollectionViewFlowLayoutGravity.Spaced) {
|
|
2174
|
+
rowLength = 2 * minimumSpacing;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
while (rowLength < maximumRowLength || maximumRowLength <= 0) {
|
|
2178
|
+
// Add items to this row until no more items fit horizontally
|
|
2179
|
+
var indexPath = this.collectionView.indexPathForObjectAtRow(sectionIndex, {inSectionAtIndex: i});
|
|
2180
|
+
|
|
2181
|
+
// When using automatic cell sizes, use the measured cell size
|
|
2182
|
+
var cellSize = (this._expectedCellSize && this.collectionView.measuredSizeOfCellAtIndexPath(indexPath)) ||
|
|
2183
|
+
this.collectionView.delegate.collectionViewSizeForCellAtIndexPath(this.collectionView, indexPath);
|
|
2184
|
+
|
|
2185
|
+
// If the item no longer fits in the row, leave it for the next one
|
|
2186
|
+
// while making sure that there is at least one object in the row, even if doesn't fit,
|
|
2187
|
+
// otherwise this will lead to an infinite loop
|
|
2188
|
+
if (cellSize[lengthAttribute] + rowLength + minimumSpacing > maximumRowLength && row.attributes.length) break;
|
|
2189
|
+
|
|
2190
|
+
var cellAttributes = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(indexPath);
|
|
2191
|
+
|
|
2192
|
+
// When using automatic cell sizes, mark these attributes as requiring a measurement
|
|
2193
|
+
cellAttributes._requireMeasurement = YES;
|
|
2194
|
+
|
|
2195
|
+
// Construct the frame
|
|
2196
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2197
|
+
cellAttributes.frame = BMRectMake(rowLength, height, cellSize.width, cellSize.height);
|
|
2198
|
+
}
|
|
2199
|
+
else {
|
|
2200
|
+
cellAttributes.frame = BMRectMake(width, rowLength, cellSize.width, cellSize.height);
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
if (rowBreadth < cellSize[breadthAttribute]) rowBreadth = cellSize[breadthAttribute];
|
|
2204
|
+
|
|
2205
|
+
// And add the attributes to the row
|
|
2206
|
+
row.attributes.push(cellAttributes);
|
|
2207
|
+
|
|
2208
|
+
rowLength += cellSize[lengthAttribute];
|
|
2209
|
+
|
|
2210
|
+
// For all cells other than the first also add the minimum spacing
|
|
2211
|
+
if (row.attributes.length > 1) {
|
|
2212
|
+
rowLength += minimumSpacing;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
// Advance to the next item
|
|
2216
|
+
sectionIndex++;
|
|
2217
|
+
|
|
2218
|
+
// If the row height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2219
|
+
if (row[rowStartAttribute] + rowBreadth > frameSize) {
|
|
2220
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
// if there is one
|
|
2224
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2225
|
+
if (sectionIndex >= sectionItems || width == 0) break;
|
|
2226
|
+
}
|
|
2227
|
+
else {
|
|
2228
|
+
if (sectionIndex >= sectionItems || height == 0) break;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
// Also continue if the number of items added to this row matches the limit of cells
|
|
2232
|
+
if (this._maximumCellsPerRow && row.attributes.length == this._maximumCellsPerRow) break;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// Skip empty rows
|
|
2236
|
+
if (row.attributes.length) {
|
|
2237
|
+
// Flow the items in this row based on the gravity and alignment
|
|
2238
|
+
// TODO: Consider lazily reflowing rows to shave the prepareLayout execution time
|
|
2239
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2240
|
+
this._reflowRow(row, {forLength: width, breadth: rowBreadth, withOccupiedLength: rowLength, spacing: minimumSpacing});
|
|
2241
|
+
|
|
2242
|
+
height += rowBreadth;
|
|
2243
|
+
|
|
2244
|
+
row.bottom = height;
|
|
2245
|
+
row.endIndex = sectionIndex - 1;
|
|
2246
|
+
section.rows.push(row);
|
|
2247
|
+
|
|
2248
|
+
height += rowSpacing;
|
|
2249
|
+
}
|
|
2250
|
+
else {
|
|
2251
|
+
this._reflowRow(row, {forLength: height, breadth: rowBreadth, withOccupiedLength: rowLength, spacing: minimumSpacing});
|
|
2252
|
+
|
|
2253
|
+
width += rowBreadth;
|
|
2254
|
+
|
|
2255
|
+
row.right = width;
|
|
2256
|
+
row.endIndex = sectionIndex - 1;
|
|
2257
|
+
section.rows.push(row);
|
|
2258
|
+
|
|
2259
|
+
width += rowSpacing;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
// After having reached the requested target, yield execution when using automatic cell sizes
|
|
2264
|
+
if (target) {
|
|
2265
|
+
// When using automatic cell size and pinned footers, preliminary footer attributes must be created
|
|
2266
|
+
if (showsFooters && this.pinsFootersToContentEdge) {
|
|
2267
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2268
|
+
let footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
2269
|
+
BMCollectionViewFlowLayoutSupplementaryView.Footer,
|
|
2270
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
2271
|
+
);
|
|
2272
|
+
|
|
2273
|
+
footerAttributes.frame = BMRectMake(left, height, width, footerHeight);
|
|
2274
|
+
footerAttributes.style = {zIndex: 1};
|
|
2275
|
+
|
|
2276
|
+
section.footerAttributes = footerAttributes;
|
|
2277
|
+
}
|
|
2278
|
+
else {
|
|
2279
|
+
let footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
2280
|
+
BMCollectionViewFlowLayoutSupplementaryView.Footer,
|
|
2281
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
2282
|
+
);
|
|
2283
|
+
|
|
2284
|
+
footerAttributes.frame = BMRectMake(width, topPadding + insetTop, footerHeight, height);
|
|
2285
|
+
if (this.pinsFootersToContentEdge) footerAttributes.style = {zIndex: 1};
|
|
2286
|
+
|
|
2287
|
+
section.footerAttributes = footerAttributes;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
if (target.indexPath) {
|
|
2292
|
+
let targetIndexPath = target.indexPath;
|
|
2293
|
+
if (targetIndexPath.section < i || (targetIndexPath.section == i && targetIndexPath.row < row.endIndex)) {
|
|
2294
|
+
// Retain how much of the layout was resolved
|
|
2295
|
+
cachedLayout.resolvedHeight = height;
|
|
2296
|
+
cachedLayout.resolvedWidth = width;
|
|
2297
|
+
cachedLayout.resolvedIndexPath = row.attributes[row.attributes.length - 1].indexPath;
|
|
2298
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2299
|
+
if (height > cachedLayout.size.height) {
|
|
2300
|
+
cachedLayout.size.height = height + rowSpacing + insetBottom + 1;
|
|
2301
|
+
if (BM_AUTOMATIC_CELL_SIZE_MESSAGES) console.log('Layout height has changed to ' + height);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
else {
|
|
2305
|
+
if (width > cachedLayout.size.width) {
|
|
2306
|
+
cachedLayout.size.width = width + rowSpacing + insetRight + 1;
|
|
2307
|
+
if (BM_AUTOMATIC_CELL_SIZE_MESSAGES) console.log('Layout width has changed to ' + width);
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
section.bottom = height;
|
|
2311
|
+
section.right = width;
|
|
2312
|
+
target = yield;
|
|
2313
|
+
|
|
2314
|
+
// When requesting a rect initially, pre-measure the cells up to that rect
|
|
2315
|
+
if (target.rect) {
|
|
2316
|
+
measureCellsInRect(target.rect);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
if (target.rect) {
|
|
2321
|
+
let targetRect = target.rect;
|
|
2322
|
+
if (height > targetRect.bottom) {
|
|
2323
|
+
// Retain how much of the layout was resolved
|
|
2324
|
+
cachedLayout.resolvedHeight = height;
|
|
2325
|
+
cachedLayout.resolvedWidth = width;
|
|
2326
|
+
cachedLayout.resolvedIndexPath = row.attributes[row.attributes.length - 1].indexPath;
|
|
2327
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2328
|
+
if (height > cachedLayout.size.height) {
|
|
2329
|
+
cachedLayout.size.height = height + rowSpacing + insetBottom + 1;
|
|
2330
|
+
if (BM_AUTOMATIC_CELL_SIZE_MESSAGES) console.log('Layout height has changed to ' + height);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
else {
|
|
2334
|
+
if (width > cachedLayout.size.width) {
|
|
2335
|
+
cachedLayout.size.width = width + rowSpacing + insetRight + 1;
|
|
2336
|
+
if (BM_AUTOMATIC_CELL_SIZE_MESSAGES) console.log('Layout width has changed to ' + width);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
section.bottom = height;
|
|
2340
|
+
section.right = width;
|
|
2341
|
+
target = yield;
|
|
2342
|
+
|
|
2343
|
+
// When requesting a rect initially, pre-measure the cells up to that rect
|
|
2344
|
+
if (target.rect) {
|
|
2345
|
+
measureCellsInRect(target.rect);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
section.resolved == YES;
|
|
2354
|
+
|
|
2355
|
+
// Remove the row spacing from the last row
|
|
2356
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2357
|
+
height -= rowSpacing;
|
|
2358
|
+
}
|
|
2359
|
+
else {
|
|
2360
|
+
width -= rowSpacing;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
|
|
2364
|
+
if (showsFooters) {
|
|
2365
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2366
|
+
height += rowSpacing;
|
|
2367
|
+
|
|
2368
|
+
// Cache the footer attributes if they are enabled
|
|
2369
|
+
var footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
2370
|
+
BMCollectionViewFlowLayoutSupplementaryView.Footer,
|
|
2371
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
2372
|
+
);
|
|
2373
|
+
|
|
2374
|
+
footerAttributes.frame = BMRectMake(left, height, width, footerHeight);
|
|
2375
|
+
if (this.pinsFootersToContentEdge) footerAttributes.style = {zIndex: 1};
|
|
2376
|
+
|
|
2377
|
+
section.footerAttributes = footerAttributes;
|
|
2378
|
+
|
|
2379
|
+
height += footerHeight;
|
|
2380
|
+
|
|
2381
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2382
|
+
if (height > frameHeight) {
|
|
2383
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
else {
|
|
2387
|
+
width += rowSpacing;
|
|
2388
|
+
|
|
2389
|
+
// Cache the footer attributes if they are enabled
|
|
2390
|
+
var footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
2391
|
+
BMCollectionViewFlowLayoutSupplementaryView.Footer,
|
|
2392
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
2393
|
+
);
|
|
2394
|
+
|
|
2395
|
+
footerAttributes.frame = BMRectMake(width, topPadding + insetTop, footerHeight, height);
|
|
2396
|
+
if (this.pinsFootersToContentEdge) footerAttributes.style = {zIndex: 1};
|
|
2397
|
+
|
|
2398
|
+
section.footerAttributes = footerAttributes;
|
|
2399
|
+
|
|
2400
|
+
width += footerHeight;
|
|
2401
|
+
|
|
2402
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2403
|
+
if (width > frameWidth) {
|
|
2404
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
|
|
2410
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2411
|
+
// Cache the bottom origin
|
|
2412
|
+
section.bottom = height;
|
|
2413
|
+
|
|
2414
|
+
height += insetBottom;
|
|
2415
|
+
|
|
2416
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2417
|
+
if (height > frameHeight) {
|
|
2418
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
else {
|
|
2422
|
+
// Cache the bottom origin
|
|
2423
|
+
section.right = width;
|
|
2424
|
+
|
|
2425
|
+
width += insetRight;
|
|
2426
|
+
|
|
2427
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2428
|
+
if (width > frameWidth) {
|
|
2429
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2436
|
+
height += bottomPadding;
|
|
2437
|
+
|
|
2438
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2439
|
+
if (height > frameHeight) {
|
|
2440
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
// If the height is lower than the collection view's height, the content gravity must be applied to all cached attributes
|
|
2444
|
+
if (height < this.collectionView.frame.size.height) {
|
|
2445
|
+
let topAdjustment = 0;
|
|
2446
|
+
if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Center) {
|
|
2447
|
+
topAdjustment = ((this.collectionView.frame.size.height - height) / 2) | 0;
|
|
2448
|
+
}
|
|
2449
|
+
else if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Bottom) {
|
|
2450
|
+
topAdjustment = this.collectionView.frame.size.height - height;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
// This represents the height that is actually used by rows, excluding spaces and supplementary views
|
|
2454
|
+
let usedHeight = 0;
|
|
2455
|
+
|
|
2456
|
+
if (topAdjustment != 0 || this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
|
|
2457
|
+
let sectionsLength = cachedLayout.sections.length;
|
|
2458
|
+
|
|
2459
|
+
// Displace the sections
|
|
2460
|
+
for (let i = 0; i < sectionsLength; i++) {
|
|
2461
|
+
let section = cachedLayout.sections[i];
|
|
2462
|
+
|
|
2463
|
+
section.top += topAdjustment;
|
|
2464
|
+
section.bottom += topAdjustment;
|
|
2465
|
+
|
|
2466
|
+
// Displace all the rows in this sections
|
|
2467
|
+
let rowsLength = section.rows.length;
|
|
2468
|
+
for (let j = 0; j < rowsLength; j++) {
|
|
2469
|
+
let row = section.rows[j];
|
|
2470
|
+
|
|
2471
|
+
row.top += topAdjustment;
|
|
2472
|
+
row.bottom += topAdjustment;
|
|
2473
|
+
|
|
2474
|
+
usedHeight += row.bottom - row.top;
|
|
2475
|
+
|
|
2476
|
+
// Displace all the cells in the row
|
|
2477
|
+
for (let k = 0; k < row.attributes.length; k++) {
|
|
2478
|
+
row.attributes[k].frame.origin.y += topAdjustment;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
// Displace the header and footer in this section
|
|
2483
|
+
if (section.headerAttributes) section.headerAttributes.frame.origin.y += topAdjustment;
|
|
2484
|
+
if (section.footerAttributes) section.footerAttributes.frame.origin.y += topAdjustment;
|
|
2485
|
+
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
// When using the expand gravity, a factor is used to multiply the heights of all rows
|
|
2490
|
+
if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
|
|
2491
|
+
const spacingSize = height - usedHeight;
|
|
2492
|
+
const usableHeight = this.collectionView.frame.size.height - spacingSize;
|
|
2493
|
+
|
|
2494
|
+
const scaleFactor = usableHeight / usedHeight;
|
|
2495
|
+
|
|
2496
|
+
let displacement = 0;
|
|
2497
|
+
|
|
2498
|
+
// Scale and displace the rows as needed
|
|
2499
|
+
for (const section of this.cachedLayout.sections) {
|
|
2500
|
+
// Displace the header and section origin
|
|
2501
|
+
section.top += displacement;
|
|
2502
|
+
if (section.headerAttributes) section.headerAttributes.frame.origin.y += displacement;
|
|
2503
|
+
|
|
2504
|
+
for (const row of section.rows) {
|
|
2505
|
+
// Displace the row origin
|
|
2506
|
+
const rowHeight = row.bottom - row.top;
|
|
2507
|
+
row.top += displacement;
|
|
2508
|
+
|
|
2509
|
+
// Scale and display each attribute's frame
|
|
2510
|
+
const scaledRowHeight = rowHeight * scaleFactor | 0;
|
|
2511
|
+
for (const attribute of row.attributes) {
|
|
2512
|
+
attribute.frame.origin.y += displacement;
|
|
2513
|
+
attribute.frame.size.height = attribute.frame.size.height * scaleFactor | 0;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
// Advance the displacement based on the row's new height then displace the row end
|
|
2517
|
+
displacement += scaledRowHeight - rowHeight;
|
|
2518
|
+
row.bottom += displacement;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
// Displace the section end and the footer
|
|
2522
|
+
section.bottom += displacement;
|
|
2523
|
+
if (section.footerAttributes) section.footerAttributes.frame.origin.y += displacement;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
cachedLayout.size = BMSizeMake(this.collectionView.frame.size.width, Math.max(height, this.collectionView.frame.size.height));
|
|
2530
|
+
}
|
|
2531
|
+
else {
|
|
2532
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2533
|
+
if (width > frameWidth) {
|
|
2534
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
// If the height is lower than the collection view's height, the content gravity must be applied to all cached attributes
|
|
2538
|
+
if (width < this.collectionView.frame.size.width) {
|
|
2539
|
+
let leftAdjustment = 0;
|
|
2540
|
+
if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Center) {
|
|
2541
|
+
leftAdjustment = ((this.collectionView.frame.size.width - width) / 2) | 0;
|
|
2542
|
+
}
|
|
2543
|
+
else if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Bottom) {
|
|
2544
|
+
leftAdjustment = this.collectionView.frame.size.width - width;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
// This represents the width that is actually used by rows, excluding spaces and supplementary views
|
|
2548
|
+
let usedWidth = 0;
|
|
2549
|
+
|
|
2550
|
+
if (leftAdjustment != 0 || this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
|
|
2551
|
+
let sectionsLength = cachedLayout.sections.length;
|
|
2552
|
+
|
|
2553
|
+
// Displace the sections
|
|
2554
|
+
for (let i = 0; i < sectionsLength; i++) {
|
|
2555
|
+
let section = cachedLayout.sections[i];
|
|
2556
|
+
|
|
2557
|
+
section.left += leftAdjustment;
|
|
2558
|
+
section.right += leftAdjustment;
|
|
2559
|
+
|
|
2560
|
+
// Displace all the rows in this sections
|
|
2561
|
+
let rowsLength = section.rows.length;
|
|
2562
|
+
for (let j = 0; j < rowsLength; j++) {
|
|
2563
|
+
let row = section.rows[j];
|
|
2564
|
+
|
|
2565
|
+
row.left += leftAdjustment;
|
|
2566
|
+
row.right += leftAdjustment;
|
|
2567
|
+
|
|
2568
|
+
usedWidth += row.right - row.left;
|
|
2569
|
+
|
|
2570
|
+
// Displace all the cells in the row
|
|
2571
|
+
for (let k = 0; k < row.attributes.length; k++) {
|
|
2572
|
+
row.attributes[k].frame.origin.x += leftAdjustment;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
// Displace the header and footer in this section
|
|
2577
|
+
if (section.headerAttributes) section.headerAttributes.frame.origin.x += leftAdjustment;
|
|
2578
|
+
if (section.footerAttributes) section.footerAttributes.frame.origin.x += leftAdjustment;
|
|
2579
|
+
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
// When using the expand gravity, a factor is used to multiply the heights of all rows
|
|
2584
|
+
if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
|
|
2585
|
+
const spacingSize = width - usedWidth;
|
|
2586
|
+
const usableWidth = this.collectionView.frame.size.width - spacingSize;
|
|
2587
|
+
|
|
2588
|
+
const scaleFactor = usableWidth / usedWidth;
|
|
2589
|
+
|
|
2590
|
+
let displacement = 0;
|
|
2591
|
+
|
|
2592
|
+
// Scale and displace the rows as needed
|
|
2593
|
+
for (const section of this.cachedLayout.sections) {
|
|
2594
|
+
// Displace the header and section origin
|
|
2595
|
+
section.left += displacement;
|
|
2596
|
+
if (section.headerAttributes) section.headerAttributes.frame.origin.x += displacement;
|
|
2597
|
+
|
|
2598
|
+
for (const row of section.rows) {
|
|
2599
|
+
// Displace the row origin
|
|
2600
|
+
const rowWidth = row.right - row.left;
|
|
2601
|
+
row.left += displacement;
|
|
2602
|
+
|
|
2603
|
+
// Scale and display each attribute's frame
|
|
2604
|
+
const scaledRowHeight = rowWidth * scaleFactor | 0;
|
|
2605
|
+
for (const attribute of row.attributes) {
|
|
2606
|
+
attribute.frame.origin.x += displacement;
|
|
2607
|
+
attribute.frame.size.height = attribute.frame.size.height * scaleFactor | 0;
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
// Advance the displacement based on the row's new width then displace the row end
|
|
2611
|
+
displacement += scaledRowHeight - rowWidth;
|
|
2612
|
+
row.right += displacement;
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
// Displace the section end and the footer
|
|
2616
|
+
section.right += displacement;
|
|
2617
|
+
if (section.footerAttributes) section.footerAttributes.frame.origin.x += displacement;
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
cachedLayout.size = BMSizeMake(Math.max(width, this.collectionView.frame.size.width), this.collectionView.frame.size.height);
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
if (this._expectedCellSize && target) this.invalidateContentSize();
|
|
2626
|
+
|
|
2627
|
+
|
|
2628
|
+
}
|
|
2629
|
+
else {
|
|
2630
|
+
// When using fixed cell sizes, the layout will pre-compute the section start and end points
|
|
2631
|
+
// and generate prototype row frames for each column count
|
|
2632
|
+
// The attributes in these prototype rows will be displaced and reused for all data set rows
|
|
2633
|
+
this.cachedLayout = { sections: [], prototypeRows: [] };
|
|
2634
|
+
var cachedLayout = this.cachedLayout;
|
|
2635
|
+
|
|
2636
|
+
let orientation = this._orientation;
|
|
2637
|
+
cachedLayout.orientation = orientation;
|
|
2638
|
+
|
|
2639
|
+
// Cache all layout properties
|
|
2640
|
+
cachedLayout.sectionInsets = this.sectionInsets;
|
|
2641
|
+
cachedLayout.rowSpacing = this.rowSpacing;
|
|
2642
|
+
cachedLayout.showsHeaders = this.showsHeaders;
|
|
2643
|
+
cachedLayout.showsFooters = this.showsFooters;
|
|
2644
|
+
cachedLayout.headerHeight = this.headerHeight;
|
|
2645
|
+
cachedLayout.footerHeight = this.footerHeight;
|
|
2646
|
+
cachedLayout.minimumSpacing = this.minimumSpacing;
|
|
2647
|
+
cachedLayout.topPadding = this.topPadding;
|
|
2648
|
+
cachedLayout.bottomPadding = this.bottomPadding;
|
|
2649
|
+
|
|
2650
|
+
let topPadding = this.topPadding;
|
|
2651
|
+
let bottomPadding = this.bottomPadding;
|
|
2652
|
+
|
|
2653
|
+
cachedLayout.pinsHeadersToContentEdge = this._pinsHeadersToContentEdge;
|
|
2654
|
+
cachedLayout.pinsFootersToContentEdge = this._pinsFootersToContentEdge;
|
|
2655
|
+
|
|
2656
|
+
let cellWidth = this.cellSize.width;
|
|
2657
|
+
let rowHeight = this.cellSize.height;
|
|
2658
|
+
let cellSize = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? cellWidth : rowHeight;
|
|
2659
|
+
let breadth = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? rowHeight : cellWidth;
|
|
2660
|
+
let rowBreadth = breadth;
|
|
2661
|
+
|
|
2662
|
+
cachedLayout.cellWidth = cellWidth;
|
|
2663
|
+
cachedLayout.rowHeight = rowHeight;
|
|
2664
|
+
cachedLayout.rowBreadth = breadth;
|
|
2665
|
+
|
|
2666
|
+
var rowSpacing = this.rowSpacing;
|
|
2667
|
+
var minimumSpacing = this.minimumSpacing;
|
|
2668
|
+
|
|
2669
|
+
// The available height is the collection view frame height minus the vertical insets and the scrollbar width
|
|
2670
|
+
// When headers, footers are used their size and the row spacing is included as well
|
|
2671
|
+
var height = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? topPadding :
|
|
2672
|
+
this.collectionView.frame.size.height - this._sectionInsets.top - this._sectionInsets.bottom - (useOffset ? this.collectionView.scrollBarSize : 0) - topPadding - bottomPadding;
|
|
2673
|
+
cachedLayout.availableHeight = height;
|
|
2674
|
+
|
|
2675
|
+
// The available width is the collection view frame width minus the horizontal insets and the scrollbar width
|
|
2676
|
+
var width = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ?
|
|
2677
|
+
this.collectionView.frame.size.width - this._sectionInsets.left - this._sectionInsets.right - (useOffset ? this.collectionView.scrollBarSize : 0) :
|
|
2678
|
+
left;
|
|
2679
|
+
cachedLayout.availableWidth = width;
|
|
2680
|
+
|
|
2681
|
+
var length = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? width : height;
|
|
2682
|
+
|
|
2683
|
+
var numberOfColumns = this._maximumCellsPerRow;
|
|
2684
|
+
if (!numberOfColumns) {
|
|
2685
|
+
if (cachedLayout.minimumSpacing) {
|
|
2686
|
+
// Otherwise it has to be computed
|
|
2687
|
+
numberOfColumns = 0;
|
|
2688
|
+
var usedLength = 0;
|
|
2689
|
+
|
|
2690
|
+
// For spaced gravity, the used width starts with at least two minimum spacings
|
|
2691
|
+
if (this._gravity === BMCollectionViewFlowLayoutGravity.Spaced) {
|
|
2692
|
+
usedLength = 2 * minimumSpacing;
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
do {
|
|
2696
|
+
// If this cell no longer fits within the row then the column limit has been reached
|
|
2697
|
+
if (usedLength && usedLength + cellSize + minimumSpacing > length) {
|
|
2698
|
+
break;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
// For every cell other than the first, also include the required minimum spacing
|
|
2702
|
+
if (numberOfColumns) {
|
|
2703
|
+
usedLength += minimumSpacing;
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
usedLength += cellSize;
|
|
2707
|
+
|
|
2708
|
+
numberOfColumns++;
|
|
2709
|
+
} while (usedLength < length && cellSize);
|
|
2710
|
+
}
|
|
2711
|
+
else {
|
|
2712
|
+
// If there is no minimum spacing required, then the number of columns is simply the available space divided
|
|
2713
|
+
// by the cell width
|
|
2714
|
+
numberOfColumns = ((length / cellSize) | 0) || 1;
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
cachedLayout.numberOfColumns = numberOfColumns;
|
|
2718
|
+
|
|
2719
|
+
// Generate prototype rows for each column count up to the maximum
|
|
2720
|
+
for (var i = 0; i < numberOfColumns; i++) {
|
|
2721
|
+
var prototypeRow = {attributes: []};
|
|
2722
|
+
|
|
2723
|
+
// When the final row is left-aligned all prototype rows have the maximum amount of columns
|
|
2724
|
+
var columns = this.leftAlignFinalRow ? numberOfColumns - 1 : i;
|
|
2725
|
+
|
|
2726
|
+
var occupiedLength = cellSize * (columns + 1) + minimumSpacing * columns;
|
|
2727
|
+
|
|
2728
|
+
// Push the prototype frames
|
|
2729
|
+
for (var j = 0; j <= columns; j++) {
|
|
2730
|
+
prototypeRow.attributes.push({frame: BMRectMake(0, 0, cellWidth, rowHeight)});
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
// And reflow the row
|
|
2734
|
+
this._reflowRow(prototypeRow, {forLength: length, breadth: breadth, withOccupiedLength: occupiedLength, spacing: minimumSpacing});
|
|
2735
|
+
|
|
2736
|
+
cachedLayout.prototypeRows.push(prototypeRow);
|
|
2737
|
+
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
// Save the section start and end points
|
|
2741
|
+
var numberOfSections = this.collectionView.numberOfSections();
|
|
2742
|
+
|
|
2743
|
+
// Each section adds height for its insets and headers and footers
|
|
2744
|
+
var insetTop = this._sectionInsets.top;
|
|
2745
|
+
var insetBottom = this._sectionInsets.bottom;
|
|
2746
|
+
let insetLeft = this._sectionInsets.left;
|
|
2747
|
+
let insetRight = this._sectionInsets.right;
|
|
2748
|
+
|
|
2749
|
+
var headerHeight = this.headerHeight;
|
|
2750
|
+
var footerHeight = this.footerHeight;
|
|
2751
|
+
|
|
2752
|
+
if (orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
2753
|
+
let height = insetTop + this._topPadding;
|
|
2754
|
+
|
|
2755
|
+
for (var i = 0; i < numberOfSections; i++) {
|
|
2756
|
+
var numberOfObjects = this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
2757
|
+
|
|
2758
|
+
var numberOfRows = Math.ceil(numberOfObjects / numberOfColumns) | 0;
|
|
2759
|
+
|
|
2760
|
+
var spacingHeight = (numberOfRows - 1) * rowSpacing;
|
|
2761
|
+
if (headerHeight) spacingHeight += rowSpacing;
|
|
2762
|
+
if (footerHeight) spacingHeight += rowSpacing;
|
|
2763
|
+
|
|
2764
|
+
let section = {
|
|
2765
|
+
top: height,
|
|
2766
|
+
bottom: height + footerHeight + headerHeight + (rowBreadth * numberOfRows) + spacingHeight,
|
|
2767
|
+
numberOfObjects,
|
|
2768
|
+
numberOfRows
|
|
2769
|
+
};
|
|
2770
|
+
cachedLayout.sections.push(section);
|
|
2771
|
+
|
|
2772
|
+
height = section.bottom + insetTop + insetBottom;
|
|
2773
|
+
|
|
2774
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2775
|
+
if (height > frameHeight) {
|
|
2776
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
height += this._bottomPadding;
|
|
2781
|
+
|
|
2782
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2783
|
+
if (height > frameHeight) {
|
|
2784
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
// If the height is lower than the collection view's height, the content gravity must be applied to all cached attributes
|
|
2788
|
+
if (height < this.collectionView.frame.size.height) {
|
|
2789
|
+
var topAdjustment = 0;
|
|
2790
|
+
if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Center) {
|
|
2791
|
+
topAdjustment = ((this.collectionView.frame.size.height - height) / 2) | 0;
|
|
2792
|
+
}
|
|
2793
|
+
else if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Bottom) {
|
|
2794
|
+
topAdjustment = this.collectionView.frame.size.height - height;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
if (topAdjustment != 0) {
|
|
2798
|
+
var sectionsLength = cachedLayout.sections.length;
|
|
2799
|
+
|
|
2800
|
+
// Displace the sections
|
|
2801
|
+
for (var i = 0; i < sectionsLength; i++) {
|
|
2802
|
+
var section = cachedLayout.sections[i];
|
|
2803
|
+
|
|
2804
|
+
section.top += topAdjustment;
|
|
2805
|
+
section.bottom += topAdjustment;
|
|
2806
|
+
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
|
|
2811
|
+
// With static sizes, all rows and attributes have the same height, so the adjustment can just be directly applied to each row
|
|
2812
|
+
let usedHeight = 0;
|
|
2813
|
+
for (const section of cachedLayout.sections) {
|
|
2814
|
+
usedHeight += section.numberOfRows * rowBreadth;
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
const spacingSize = height - usedHeight;
|
|
2818
|
+
const scaleFactor = (this.collectionView.frame.size.height - spacingSize) / usedHeight;
|
|
2819
|
+
|
|
2820
|
+
// Multiply the height of the prototype rows by the scale factor
|
|
2821
|
+
for (const row of cachedLayout.prototypeRows) {
|
|
2822
|
+
for (const attribute of row.attributes) {
|
|
2823
|
+
attribute.frame.size.height = attribute.frame.size.height * scaleFactor | 0;
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
// Adjust the bounds of each section as needed
|
|
2828
|
+
let displacement = 0;
|
|
2829
|
+
for (const section of cachedLayout.sections) {
|
|
2830
|
+
section.top += displacement;
|
|
2831
|
+
|
|
2832
|
+
const sectionHeight = section.numberOfRows * rowBreadth;
|
|
2833
|
+
displacement += (sectionHeight * scaleFactor | 0) - sectionHeight;
|
|
2834
|
+
|
|
2835
|
+
section.bottom += displacement;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
cachedLayout.rowHeight = cachedLayout.rowHeight * scaleFactor | 0;
|
|
2839
|
+
cachedLayout.rowBreadth = cachedLayout.rowBreadth * scaleFactor | 0;
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
else {
|
|
2844
|
+
let width = insetLeft;
|
|
2845
|
+
|
|
2846
|
+
for (var i = 0; i < numberOfSections; i++) {
|
|
2847
|
+
var numberOfObjects = this.collectionView.numberOfObjectsInSectionAtIndex(i);
|
|
2848
|
+
|
|
2849
|
+
var numberOfRows = Math.ceil(numberOfObjects / numberOfColumns) | 0;
|
|
2850
|
+
|
|
2851
|
+
var spacingWidth = (numberOfRows - 1) * rowSpacing;
|
|
2852
|
+
if (headerHeight) spacingWidth += rowSpacing;
|
|
2853
|
+
if (footerHeight) spacingWidth += rowSpacing;
|
|
2854
|
+
|
|
2855
|
+
let section = {
|
|
2856
|
+
left: width,
|
|
2857
|
+
right: width + (breadth * numberOfRows) + spacingWidth,
|
|
2858
|
+
numberOfObjects,
|
|
2859
|
+
numberOfRows
|
|
2860
|
+
};
|
|
2861
|
+
cachedLayout.sections.push(section);
|
|
2862
|
+
|
|
2863
|
+
width = section.right + insetLeft + insetRight;
|
|
2864
|
+
|
|
2865
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2866
|
+
if (width > frameWidth) {
|
|
2867
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
// If the total height exceeds the collection view frame height, perform a second layout pass to account for the scrollbars
|
|
2872
|
+
if (width > frameWidth) {
|
|
2873
|
+
if (this.collectionView.scrollBarSize && !useOffset) return this._prepareLayoutWithScrollbarOffset(YES);
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
// If the height is lower than the collection view's height, the content gravity must be applied to all cached attributes
|
|
2877
|
+
if (width < this.collectionView.frame.size.width) {
|
|
2878
|
+
var leftAdjustment = 0;
|
|
2879
|
+
if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Center) {
|
|
2880
|
+
leftAdjustment = ((this.collectionView.frame.size.width - width) / 2) | 0;
|
|
2881
|
+
}
|
|
2882
|
+
else if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Bottom) {
|
|
2883
|
+
leftAdjustment = this.collectionView.frame.size.width - width;
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
if (leftAdjustment != 0) {
|
|
2887
|
+
var sectionsLength = cachedLayout.sections.length;
|
|
2888
|
+
|
|
2889
|
+
// Displace the sections
|
|
2890
|
+
for (var i = 0; i < sectionsLength; i++) {
|
|
2891
|
+
var section = cachedLayout.sections[i];
|
|
2892
|
+
|
|
2893
|
+
section.left += leftAdjustment;
|
|
2894
|
+
section.right += leftAdjustment;
|
|
2895
|
+
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
if (this._contentGravity === BMCollectionViewFlowLayoutAlignment.Expand) {
|
|
2900
|
+
// With static sizes, all rows and attributes have the same width, so the adjustment can just be directly applied to each row
|
|
2901
|
+
let usedWidth = 0;
|
|
2902
|
+
for (const section of cachedLayout.sections) {
|
|
2903
|
+
usedWidth += section.numberOfRows * rowBreadth;
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
const spacingSize = width - usedWidth;
|
|
2907
|
+
const scaleFactor = (this.collectionView.frame.size.width - spacingSize) / usedWidth;
|
|
2908
|
+
|
|
2909
|
+
// Multiply the width of the prototype rows by the scale factor
|
|
2910
|
+
for (const row of cachedLayout.prototypeRows) {
|
|
2911
|
+
for (const attribute of row.attributes) {
|
|
2912
|
+
attribute.frame.size.width = attribute.frame.size.width * scaleFactor | 0;
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
// Adjust the bounds of each section as needed
|
|
2917
|
+
let displacement = 0;
|
|
2918
|
+
for (const section of cachedLayout.sections) {
|
|
2919
|
+
section.left += displacement;
|
|
2920
|
+
|
|
2921
|
+
const sectionWidth = section.numberOfRows * rowBreadth;
|
|
2922
|
+
displacement += (sectionWidth * scaleFactor | 0) - sectionWidth;
|
|
2923
|
+
|
|
2924
|
+
section.right += displacement;
|
|
2925
|
+
}
|
|
2926
|
+
cachedLayout.cellWidth = cachedLayout.cellWidth * scaleFactor | 0;
|
|
2927
|
+
cachedLayout.rowBreadth = cachedLayout.rowBreadth * scaleFactor | 0;
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
},
|
|
2935
|
+
|
|
2936
|
+
// @override - BMCollectionViewLayout
|
|
2937
|
+
constraintsForMeasuringCell(cell, {atIndexPath}) {
|
|
2938
|
+
// In flow layout, a cells's width cannot exceed the collection view's frame
|
|
2939
|
+
return [BMLayoutConstraint.constraintWithView(cell, {
|
|
2940
|
+
attribute: BMLayoutAttribute.Width,
|
|
2941
|
+
relatedBy: BMLayoutConstraintRelation.LessThanOrEquals,
|
|
2942
|
+
constant: this.collectionView.frame.size.width - this.sectionInsets.left - this.sectionInsets.right
|
|
2943
|
+
})]
|
|
2944
|
+
},
|
|
2945
|
+
|
|
2946
|
+
/**
|
|
2947
|
+
* @deprecated Not used anymore
|
|
2948
|
+
* Reflows a cached row object based on the current alignment and gravity.
|
|
2949
|
+
* @param row <Object> The cached row object.
|
|
2950
|
+
* {
|
|
2951
|
+
* @param forWidth <Number> The row's width.
|
|
2952
|
+
* @param height <Number> The row's height.
|
|
2953
|
+
* @param withOccupiedWidth <Number> The width occupied by the cells.
|
|
2954
|
+
* @param spacing <Number> The minimum spacing to use between the cells.
|
|
2955
|
+
* }
|
|
2956
|
+
*/
|
|
2957
|
+
reflowRow: function (row, options) {
|
|
2958
|
+
var width = options.forWidth;
|
|
2959
|
+
var rowWidth = options.withOccupiedWidth;
|
|
2960
|
+
|
|
2961
|
+
var rowHeight = options.height;
|
|
2962
|
+
|
|
2963
|
+
var spacing = options.spacing;
|
|
2964
|
+
|
|
2965
|
+
// Compute the width occupied by the spacing
|
|
2966
|
+
var spacingSize = spacing * (row.attributes.length - 1);
|
|
2967
|
+
//rowWidth += spacingSize;
|
|
2968
|
+
|
|
2969
|
+
// Compute the width occupied by the actual cells, without the spacing
|
|
2970
|
+
var cellOccupiedWidth = rowWidth - spacingSize;
|
|
2971
|
+
|
|
2972
|
+
var remainingSpace = width - rowWidth;
|
|
2973
|
+
|
|
2974
|
+
var gravity = this._gravity;
|
|
2975
|
+
var alignment = this._alignment;
|
|
2976
|
+
|
|
2977
|
+
var cellCount = row.attributes.length;
|
|
2978
|
+
|
|
2979
|
+
// When the items flow horizontally they start out at left margin
|
|
2980
|
+
// Their width is multiplied by the widthMultiplier
|
|
2981
|
+
// Then the next item is placed after spacing pixels from the previous one
|
|
2982
|
+
var widthMultiplier = gravity == BMCollectionViewFlowLayoutGravity.Expand ? (width - spacingSize) / cellOccupiedWidth : 1;
|
|
2983
|
+
// Because the width multiplier may lead to fractional values for the widths
|
|
2984
|
+
// which are then truncated to integer sizes, the additional pixels are accounted for
|
|
2985
|
+
// by adding 1 pixel to the width of cells until the entire space is used
|
|
2986
|
+
var extraPixels = 0;
|
|
2987
|
+
if (gravity == BMCollectionViewFlowLayoutGravity.Expand) {
|
|
2988
|
+
var expandedOccupiedWidth = 0;
|
|
2989
|
+
for (var i = 0; i < cellCount; i++) {
|
|
2990
|
+
expandedOccupiedWidth += (row.attributes[i].frame.size.width * widthMultiplier) | 0;
|
|
2991
|
+
}
|
|
2992
|
+
extraPixels = ((width - spacingSize) | 0) % (expandedOccupiedWidth | 0);
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
// The spacing extra pixels is similar to the extraPixels variable, except that it is used for the other gravities
|
|
2996
|
+
// and is added to spacing other than the cell sizes
|
|
2997
|
+
var spacingExtraPixels = 0;
|
|
2998
|
+
|
|
2999
|
+
var leftMargin = this._sectionInsets.left;
|
|
3000
|
+
|
|
3001
|
+
// Implicitly, for left gravity, the leftMargin does not change
|
|
3002
|
+
if (gravity === BMCollectionViewFlowLayoutGravity.Center) {
|
|
3003
|
+
leftMargin = (leftMargin + (width - rowWidth) / 2) | 0;
|
|
3004
|
+
}
|
|
3005
|
+
else if (gravity === BMCollectionViewFlowLayoutGravity.End) {
|
|
3006
|
+
leftMargin = (leftMargin + width - rowWidth) | 0;
|
|
3007
|
+
}
|
|
3008
|
+
else if (gravity === BMCollectionViewFlowLayoutGravity.Edge) {
|
|
3009
|
+
spacing = ((width - cellOccupiedWidth) / (cellCount - 1)) | 0;
|
|
3010
|
+
|
|
3011
|
+
// Compute the extra spacing pixels as the remainder between the row width and the total occupied integer width
|
|
3012
|
+
spacingExtraPixels = width - (spacing * (cellCount - 1) + cellOccupiedWidth);
|
|
3013
|
+
}
|
|
3014
|
+
else if (gravity === BMCollectionViewFlowLayoutGravity.Spaced) {
|
|
3015
|
+
spacing = ((width - cellOccupiedWidth) / (cellCount + 1)) | 0;
|
|
3016
|
+
|
|
3017
|
+
// Compute the extra spacing pixels as the remainder between the row width and the total occupied integer width
|
|
3018
|
+
spacingExtraPixels = width - (spacing * (cellCount + 1) + cellOccupiedWidth);
|
|
3019
|
+
|
|
3020
|
+
leftMargin = leftMargin + spacing;
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
// Flow the items horizontally and align them vertially
|
|
3024
|
+
for (var i = 0; i < cellCount; i++) {
|
|
3025
|
+
var cellAttributes = row.attributes[i];
|
|
3026
|
+
|
|
3027
|
+
// Flow the item horizontally
|
|
3028
|
+
cellAttributes.frame.origin.x = leftMargin;
|
|
3029
|
+
cellAttributes.frame.size.width *= widthMultiplier;
|
|
3030
|
+
// Constrain the width to an integer size
|
|
3031
|
+
cellAttributes.frame.size.width = cellAttributes.frame.size.width | 0;
|
|
3032
|
+
|
|
3033
|
+
// Add the additional extra pixel if needed, then subtract from the remaining pixels
|
|
3034
|
+
if (extraPixels) {
|
|
3035
|
+
cellAttributes.frame.size.width += 1;
|
|
3036
|
+
extraPixels--;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
leftMargin += spacing + cellAttributes.frame.size.width;
|
|
3040
|
+
// Add the additional spacing extra pixel if needed, then subtract from the remaining pixels
|
|
3041
|
+
if (spacingExtraPixels) {
|
|
3042
|
+
leftMargin += 1;
|
|
3043
|
+
spacingExtraPixels--;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
// Align it vertically
|
|
3047
|
+
if (alignment === BMCollectionViewFlowLayoutAlignment.Center) {
|
|
3048
|
+
cellAttributes.frame.origin.y += ((rowHeight - cellAttributes.frame.size.height) / 2) | 0;
|
|
3049
|
+
}
|
|
3050
|
+
else if (alignment === BMCollectionViewFlowLayoutAlignment.Bottom) {
|
|
3051
|
+
cellAttributes.frame.origin.y += rowHeight - cellAttributes.frame.size.height;
|
|
3052
|
+
}
|
|
3053
|
+
else if (alignment === BMCollectionViewFlowLayoutAlignment.Expand) {
|
|
3054
|
+
cellAttributes.frame.size.height = rowHeight;
|
|
3055
|
+
}
|
|
3056
|
+
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
},
|
|
3060
|
+
|
|
3061
|
+
|
|
3062
|
+
/**
|
|
3063
|
+
* Reflows a cached row object based on the current alignment, axis and gravity.
|
|
3064
|
+
* @param row <Object> The cached row object.
|
|
3065
|
+
* {
|
|
3066
|
+
* @param forLength <Number> The row's size along the secondary axis.
|
|
3067
|
+
* @param breadth <Number> The row's size along the primary axis.
|
|
3068
|
+
* @param withOccupiedLength <Number> The size occupied by the cells along the secondary axis.
|
|
3069
|
+
* @param spacing <Number> The minimum spacing to use between the cells.
|
|
3070
|
+
* }
|
|
3071
|
+
*/
|
|
3072
|
+
_reflowRow: function (row, {forLength: length, breadth, withOccupiedLength: occupiedLength, spacing}) {
|
|
3073
|
+
//var length = options.forWidth;
|
|
3074
|
+
//var occupiedLength = options.withOccupiedWidth;
|
|
3075
|
+
|
|
3076
|
+
//var breadth = options.height;
|
|
3077
|
+
|
|
3078
|
+
//var spacing = options.spacing;
|
|
3079
|
+
|
|
3080
|
+
// Compute the width occupied by the spacing
|
|
3081
|
+
var spacingSize = spacing * (row.attributes.length - 1);
|
|
3082
|
+
//rowWidth += spacingSize;
|
|
3083
|
+
|
|
3084
|
+
// Compute the width occupied by the actual cells, without the spacing
|
|
3085
|
+
var cellOccupiedLength = occupiedLength - spacingSize;
|
|
3086
|
+
|
|
3087
|
+
var remainingSpace = length - occupiedLength;
|
|
3088
|
+
|
|
3089
|
+
var gravity = this._gravity;
|
|
3090
|
+
var alignment = this._alignment;
|
|
3091
|
+
let orientation = this._orientation;
|
|
3092
|
+
let lengthAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'width' : 'height';
|
|
3093
|
+
let secondaryLengthAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'height' : 'width';
|
|
3094
|
+
let positionAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'x' : 'y';
|
|
3095
|
+
let secondaryPositionAttribute = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'y' : 'x';
|
|
3096
|
+
|
|
3097
|
+
var cellCount = row.attributes.length;
|
|
3098
|
+
|
|
3099
|
+
// When the items flow horizontally they start out at left margin
|
|
3100
|
+
// Their width is multiplied by the widthMultiplier
|
|
3101
|
+
// Then the next item is placed after spacing pixels from the previous one
|
|
3102
|
+
var lengthMultiplier = gravity == BMCollectionViewFlowLayoutGravity.Expand ? (length - spacingSize) / cellOccupiedLength : 1;
|
|
3103
|
+
// Because the width multiplier may lead to fractional values for the widths
|
|
3104
|
+
// which are then truncated to integer sizes, the additional pixels are accounted for
|
|
3105
|
+
// by adding 1 pixel to the width of cells until the entire space is used
|
|
3106
|
+
var extraPixels = 0;
|
|
3107
|
+
if (gravity == BMCollectionViewFlowLayoutGravity.Expand) {
|
|
3108
|
+
var expandedOccupiedLength = 0;
|
|
3109
|
+
for (var i = 0; i < cellCount; i++) {
|
|
3110
|
+
expandedOccupiedLength += (row.attributes[i].frame.size[lengthAttribute] * lengthMultiplier) | 0;
|
|
3111
|
+
}
|
|
3112
|
+
extraPixels = ((length - spacingSize) | 0) % (expandedOccupiedLength | 0);
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
// The spacing extra pixels is similar to the extraPixels variable, except that it is used for the other gravities
|
|
3116
|
+
// and is added to spacing other than the cell sizes
|
|
3117
|
+
var spacingExtraPixels = 0;
|
|
3118
|
+
|
|
3119
|
+
var startMargin = orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? this._sectionInsets.left : this._sectionInsets.top + this.topPadding;
|
|
3120
|
+
|
|
3121
|
+
// Implicitly, for left gravity, the leftMargin does not change
|
|
3122
|
+
if (gravity === BMCollectionViewFlowLayoutGravity.Center) {
|
|
3123
|
+
startMargin = (startMargin + (length - occupiedLength) / 2) | 0;
|
|
3124
|
+
}
|
|
3125
|
+
else if (gravity === BMCollectionViewFlowLayoutGravity.End) {
|
|
3126
|
+
startMargin = (startMargin + length - occupiedLength) | 0;
|
|
3127
|
+
}
|
|
3128
|
+
else if (gravity === BMCollectionViewFlowLayoutGravity.Edge) {
|
|
3129
|
+
spacing = ((length - cellOccupiedLength) / (cellCount - 1)) | 0;
|
|
3130
|
+
|
|
3131
|
+
// Compute the extra spacing pixels as the remainder between the row width and the total occupied integer width
|
|
3132
|
+
spacingExtraPixels = length - (spacing * (cellCount - 1) + cellOccupiedLength);
|
|
3133
|
+
}
|
|
3134
|
+
else if (gravity === BMCollectionViewFlowLayoutGravity.Spaced) {
|
|
3135
|
+
spacing = ((length - cellOccupiedLength) / (cellCount + 1)) | 0;
|
|
3136
|
+
|
|
3137
|
+
// Compute the extra spacing pixels as the remainder between the row width and the total occupied integer width
|
|
3138
|
+
spacingExtraPixels = length - (spacing * (cellCount + 1) + cellOccupiedLength);
|
|
3139
|
+
|
|
3140
|
+
startMargin = startMargin + spacing;
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
// Flow the items horizontally and align them vertially
|
|
3144
|
+
for (var i = 0; i < cellCount; i++) {
|
|
3145
|
+
var cellAttributes = row.attributes[i];
|
|
3146
|
+
|
|
3147
|
+
// Flow the item horizontally
|
|
3148
|
+
cellAttributes.frame.origin[positionAttribute] = startMargin;
|
|
3149
|
+
cellAttributes.frame.size[lengthAttribute] *= lengthMultiplier;
|
|
3150
|
+
// Constrain the width to an integer size
|
|
3151
|
+
cellAttributes.frame.size[lengthAttribute] = cellAttributes.frame.size[lengthAttribute] | 0;
|
|
3152
|
+
|
|
3153
|
+
// Add the additional extra pixel if needed, then subtract from the remaining pixels
|
|
3154
|
+
if (extraPixels) {
|
|
3155
|
+
cellAttributes.frame.size[lengthAttribute] += 1;
|
|
3156
|
+
extraPixels--;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
startMargin += spacing + cellAttributes.frame.size[lengthAttribute];
|
|
3160
|
+
// Add the additional spacing extra pixel if needed, then subtract from the remaining pixels
|
|
3161
|
+
if (spacingExtraPixels) {
|
|
3162
|
+
startMargin += 1;
|
|
3163
|
+
spacingExtraPixels--;
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
// Align it vertically
|
|
3167
|
+
if (alignment === BMCollectionViewFlowLayoutAlignment.Center) {
|
|
3168
|
+
cellAttributes.frame.origin[secondaryPositionAttribute] += ((breadth - cellAttributes.frame.size[secondaryLengthAttribute]) / 2) | 0;
|
|
3169
|
+
}
|
|
3170
|
+
else if (alignment === BMCollectionViewFlowLayoutAlignment.Bottom) {
|
|
3171
|
+
cellAttributes.frame.origin[secondaryPositionAttribute] += breadth - cellAttributes.frame.size[secondaryLengthAttribute];
|
|
3172
|
+
}
|
|
3173
|
+
else if (alignment === BMCollectionViewFlowLayoutAlignment.Expand) {
|
|
3174
|
+
cellAttributes.frame.size[secondaryLengthAttribute] = breadth;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
}
|
|
3178
|
+
},
|
|
3179
|
+
|
|
3180
|
+
// @override
|
|
3181
|
+
contentSize: function () {
|
|
3182
|
+
|
|
3183
|
+
if (!this._cellSize || this._expectedCellSize) return this.cachedLayout.size;
|
|
3184
|
+
|
|
3185
|
+
// The size should never be smaller than the collection view frame
|
|
3186
|
+
if (this._orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3187
|
+
return this.cachedLayout.sections.length ?
|
|
3188
|
+
BMSizeMake(this.collectionView.frame.size.width,
|
|
3189
|
+
Math.max(this.cachedLayout.sections[this.cachedLayout.sections.length - 1].bottom + this._bottomPadding + this.sectionInsets.bottom,
|
|
3190
|
+
this.collectionView.frame.size.height)) :
|
|
3191
|
+
this.collectionView.frame.size.copy();
|
|
3192
|
+
}
|
|
3193
|
+
else {
|
|
3194
|
+
return this.cachedLayout.sections.length ?
|
|
3195
|
+
BMSizeMake(Math.max(this.cachedLayout.sections[this.cachedLayout.sections.length - 1].right + this.sectionInsets.right,
|
|
3196
|
+
this.collectionView.frame.size.width),
|
|
3197
|
+
this.collectionView.frame.size.height) :
|
|
3198
|
+
this.collectionView.frame.size.copy();
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
|
|
3202
|
+
},
|
|
3203
|
+
|
|
3204
|
+
// @override - BMCollectionViewTableLayout
|
|
3205
|
+
shouldInvalidateLayoutForFrameChange(frame, {fromFrame}) {
|
|
3206
|
+
if (fromFrame && fromFrame.size.width < frame.size.width) {
|
|
3207
|
+
// If the size increases, invalidate all existing measured cells as their measured size can be less than the
|
|
3208
|
+
// collection view's frame size as a result of how text wraps
|
|
3209
|
+
this.collectionView.invalidateMeasuredSizeOfCells();
|
|
3210
|
+
}
|
|
3211
|
+
else {
|
|
3212
|
+
// Because the measured size of the cells can depend upon the size of collection view's frame, in addition
|
|
3213
|
+
// to invalidating the layout, flow layout also invalidates the size of cells that are as wide as collection view's previous frame.
|
|
3214
|
+
this.collectionView.invalidateMeasuredSizeOfCellsWithBlock(size => {
|
|
3215
|
+
return size.width >= this.cachedLayout.availableWidth;
|
|
3216
|
+
});
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
return YES;
|
|
3220
|
+
},
|
|
3221
|
+
|
|
3222
|
+
// @override - BMCollectionViewLayout
|
|
3223
|
+
shouldInvalidateLayoutForBoundsChange: function (bounds) {
|
|
3224
|
+
var shouldInvalidate = this._pinsHeadersToContentEdge || this._pinsFootersToContentEdge;
|
|
3225
|
+
|
|
3226
|
+
// When using automatic cell sizes, calculate the rest of the layout up to the given bounds, if needed
|
|
3227
|
+
// and invalidate the layout appropriately
|
|
3228
|
+
if (this._orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3229
|
+
if (this._expectedCellSize && bounds.bottom > this.cachedLayout.resolvedHeight) {
|
|
3230
|
+
this._layoutIterator.next({rect: bounds});
|
|
3231
|
+
shouldInvalidate = YES;
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
else {
|
|
3235
|
+
if (this._expectedCellSize && bounds.right > this.cachedLayout.resolvedWidth) {
|
|
3236
|
+
this._layoutIterator.next({rect: bounds});
|
|
3237
|
+
shouldInvalidate = YES;
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
if (shouldInvalidate) {
|
|
3242
|
+
// While scrolling, it is not needed to recalculate the layout caches.
|
|
3243
|
+
this.isChangingBounds = YES;
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
return shouldInvalidate;
|
|
3247
|
+
},
|
|
3248
|
+
|
|
3249
|
+
// @override - BMCollectionViewLayout
|
|
3250
|
+
didInvalidateLayoutForBoundsChange: function () {
|
|
3251
|
+
this.isChangingBounds = NO;
|
|
3252
|
+
},
|
|
3253
|
+
|
|
3254
|
+
|
|
3255
|
+
// @override
|
|
3256
|
+
attributesForCellAtIndexPath: function (indexPath) {
|
|
3257
|
+
// When using automatic cell sizes, it is important to ensure that the sizing information has been computed
|
|
3258
|
+
// for the requested index path
|
|
3259
|
+
if (this._expectedCellSize) {
|
|
3260
|
+
if (this.cachedLayout.resolvedIndexPath.section < indexPath.section ||
|
|
3261
|
+
(this.cachedLayout.resolvedIndexPath.section == indexPath.section && this.cachedLayout.resolvedIndexPath.row < indexPath.row)) {
|
|
3262
|
+
// If the layout has not been resolved up to this index path, continue computing it until it has
|
|
3263
|
+
this._layoutIterator.next({indexPath: indexPath});
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
// Return the cached attributes when using dynamic sizes
|
|
3268
|
+
if (!this._cellSize || this._expectedCellSize) return this.cachedAttributesForCellAtIndexPath(indexPath);
|
|
3269
|
+
|
|
3270
|
+
// Otherwise compute the attributes
|
|
3271
|
+
return this.computedAttributesForCellAtIndexPath(indexPath);
|
|
3272
|
+
|
|
3273
|
+
},
|
|
3274
|
+
|
|
3275
|
+
/**
|
|
3276
|
+
* Returns the cached cell attributes for the cell at the given index path.
|
|
3277
|
+
* This method should only be used when using variable cell sizes.
|
|
3278
|
+
* @param indexPath <BMIndexPath> The cell's index path.
|
|
3279
|
+
* {
|
|
3280
|
+
* @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
|
|
3281
|
+
* }
|
|
3282
|
+
* @return <BMCollectionViewLayoutAttributes> The corresponding cell attributes.
|
|
3283
|
+
* @throw If there are no cached attributes for the specified index path.
|
|
3284
|
+
*/
|
|
3285
|
+
cachedAttributesForCellAtIndexPath: function (indexPath, options) {
|
|
3286
|
+
var cache = (options && options.usingCache) || this.cachedLayout;
|
|
3287
|
+
|
|
3288
|
+
// Get the cached section corresponding to the index path
|
|
3289
|
+
var sectionRows = cache.sections[indexPath.section].rows;
|
|
3290
|
+
var index = indexPath.row;
|
|
3291
|
+
|
|
3292
|
+
// Find the row in which the index path is located
|
|
3293
|
+
var sectionRowsLength = sectionRows.length;
|
|
3294
|
+
for (var i = 0; i < sectionRowsLength; i++) {
|
|
3295
|
+
var row = sectionRows[i];
|
|
3296
|
+
|
|
3297
|
+
if (index >= row.startIndex && index <= row.endIndex) {
|
|
3298
|
+
return row.attributes[index - row.startIndex];
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
throw 'Unable to find the cached attributes for the cell at the given index path';
|
|
3304
|
+
},
|
|
3305
|
+
|
|
3306
|
+
/**
|
|
3307
|
+
* Computes and returns the attributes for the cell at the given index path.
|
|
3308
|
+
* This method should only be used when using fixed cell sizes.
|
|
3309
|
+
* @param indexPath <BMIndexPath> The cell's index path.
|
|
3310
|
+
* {
|
|
3311
|
+
* @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
|
|
3312
|
+
* }
|
|
3313
|
+
* @return <BMCollectionViewLayoutAttributes> The corresponding cell attributes.
|
|
3314
|
+
* @throws If the index path is out of bounds.
|
|
3315
|
+
*/
|
|
3316
|
+
computedAttributesForCellAtIndexPath: function (indexPath, options) {
|
|
3317
|
+
var cache = (options && options.usingCache) || this.cachedLayout;
|
|
3318
|
+
|
|
3319
|
+
var sectionIndex = indexPath.section;
|
|
3320
|
+
var section = cache.sections[sectionIndex];
|
|
3321
|
+
|
|
3322
|
+
var numberOfColumns = cache.numberOfColumns;
|
|
3323
|
+
var numberOfObjects = section.numberOfObjects;
|
|
3324
|
+
|
|
3325
|
+
var objectIndex = indexPath.row;
|
|
3326
|
+
|
|
3327
|
+
// Find the row and column index within the section
|
|
3328
|
+
var cellRow = (objectIndex / numberOfColumns) | 0;
|
|
3329
|
+
var cellColumn = objectIndex % numberOfColumns;
|
|
3330
|
+
|
|
3331
|
+
// Find this cell's row prototype. In most cases this will be the prototype for the numberOfColumns columns,
|
|
3332
|
+
// unless this cell is in the last row - in those cases it is possible that there may be fewer columns
|
|
3333
|
+
if (cellRow == ((numberOfObjects / numberOfColumns) | 0)) {
|
|
3334
|
+
var numberOfColumnsInLastRow = (numberOfObjects % numberOfColumns) || numberOfColumns;
|
|
3335
|
+
|
|
3336
|
+
var prototypeRow = cache.prototypeRows[numberOfColumnsInLastRow - 1];
|
|
3337
|
+
}
|
|
3338
|
+
else {
|
|
3339
|
+
var prototypeRow = cache.prototypeRows[numberOfColumns - 1];
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
var attributes;
|
|
3343
|
+
|
|
3344
|
+
// Find out where the row starts
|
|
3345
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3346
|
+
let rowTop = section.top;
|
|
3347
|
+
if (cache.showsHeaders) {
|
|
3348
|
+
rowTop += cache.headerHeight + cache.rowSpacing;
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
rowTop += cellRow * (cache.rowHeight + cache.rowSpacing);
|
|
3352
|
+
|
|
3353
|
+
// Construct the attributes
|
|
3354
|
+
attributes = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(indexPath);
|
|
3355
|
+
attributes.frame = prototypeRow.attributes[cellColumn].frame.copy();
|
|
3356
|
+
attributes.frame.origin.y = rowTop;
|
|
3357
|
+
}
|
|
3358
|
+
else {
|
|
3359
|
+
let rowLeft = section.left;
|
|
3360
|
+
if (cache.showsHeaders) {
|
|
3361
|
+
rowLeft += cache.headerHeight + cache.rowSpacing;
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
rowLeft += cellRow * (cache.rowBreadth + cache.rowSpacing);
|
|
3365
|
+
|
|
3366
|
+
// Construct the attributes
|
|
3367
|
+
attributes = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(indexPath);
|
|
3368
|
+
attributes.frame = prototypeRow.attributes[cellColumn].frame.copy();
|
|
3369
|
+
attributes.frame.origin.x = rowLeft;
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
return attributes;
|
|
3373
|
+
|
|
3374
|
+
},
|
|
3375
|
+
|
|
3376
|
+
// @override
|
|
3377
|
+
attributesForSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
3378
|
+
|
|
3379
|
+
// The empty supplementary view will always ever have one instance with a preset index path regardless of whether the layout is cached or computed
|
|
3380
|
+
if (identifier === BMCollectionViewTableLayoutSupplementaryView.Empty) {
|
|
3381
|
+
var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
3382
|
+
BMCollectionViewTableLayoutSupplementaryView.Empty,
|
|
3383
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([0])}
|
|
3384
|
+
);
|
|
3385
|
+
|
|
3386
|
+
attribute.frame = BMRectMake(0, 0, this.collectionView.frame.size.width, this.collectionView.frame.size.height);
|
|
3387
|
+
return attribute;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
if (this._expectedCellSize) {
|
|
3391
|
+
if (this.cachedLayout.resolvedIndexPath.section < options.atIndexPath.section ||
|
|
3392
|
+
(this.cachedLayout.resolvedIndexPath.section == options.atIndexPath.section && identifier == BMCollectionViewFlowLayoutSupplementaryView.Footer)) {
|
|
3393
|
+
if (options.atIndexPath.section == this.collectionView.numberOfSections() - 1) {
|
|
3394
|
+
// If there is no following section, compute until the last index path
|
|
3395
|
+
this._layoutIterator.next({indexPath: this.collectionView.indexPathForObjectAtRow(this.collectionView.numberOfObjectsInSectionAtIndex(options.atIndexPath.section) - 1, {inSectionAtIndex: options.atIndexPath.section})});
|
|
3396
|
+
}
|
|
3397
|
+
else {
|
|
3398
|
+
// If the layout has not been resolved up to this index path, continue computing up to the beginning of the next section
|
|
3399
|
+
this._layoutIterator.next({indexPath: this.collectionView.indexPathForObjectAtRow(0, {inSectionAtIndex: options.atIndexPath.section + 1})});
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
// Return the cached attributes when using dynamic sizes
|
|
3405
|
+
if (!this._cellSize || this._expectedCellSize) return this.cachedAttributesForSupplementaryViewWithIdentifier(identifier, options);
|
|
3406
|
+
|
|
3407
|
+
// Otherwise compute the attributes
|
|
3408
|
+
return this.computedAttributesForSupplementaryViewWithIdentifier(identifier, options);
|
|
3409
|
+
|
|
3410
|
+
},
|
|
3411
|
+
|
|
3412
|
+
|
|
3413
|
+
/**
|
|
3414
|
+
* Returns the cached cell attributes for the supplementary view with the given identifier at the given index path.
|
|
3415
|
+
* This method should only be used when using variable cell sizes.
|
|
3416
|
+
* @param identifier <String> The supplementary view's type.
|
|
3417
|
+
* {
|
|
3418
|
+
* @param atIndexPath <BMIndexPath> The supplementary view's index path.
|
|
3419
|
+
* @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
|
|
3420
|
+
* }
|
|
3421
|
+
* @return <BMCollectionViewLayoutAttributes, nullable> The corresponding cell attributes, or undefined if there are no cached attributes for the specified index path
|
|
3422
|
+
* or the requested supplementary view type is unsupported.
|
|
3423
|
+
*/
|
|
3424
|
+
cachedAttributesForSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
3425
|
+
var cache = (options && options.usingCache) || this.cachedLayout;
|
|
3426
|
+
var section = options.atIndexPath.section;
|
|
3427
|
+
|
|
3428
|
+
if (section >= cache.sections.length) return undefined;
|
|
3429
|
+
|
|
3430
|
+
if (identifier === BMCollectionViewFlowLayoutSupplementaryView.Header) {
|
|
3431
|
+
return cache.sections[section].headerAttributes;
|
|
3432
|
+
}
|
|
3433
|
+
else if (identifier === BMCollectionViewFlowLayoutSupplementaryView.Footer) {
|
|
3434
|
+
return cache.sections[section].footerAttributes;
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
// Other types of supplementary views are not supported.
|
|
3438
|
+
return undefined;
|
|
3439
|
+
},
|
|
3440
|
+
|
|
3441
|
+
|
|
3442
|
+
/**
|
|
3443
|
+
* Computes and returns the cell attributes for the supplementary view with the given identifier at the given index path.
|
|
3444
|
+
* This method should only be used when using fixed cell sizes.
|
|
3445
|
+
* @param identifier <String> The supplementary view's type.
|
|
3446
|
+
* {
|
|
3447
|
+
* @param atIndexPath <BMIndexPath> The supplementary view's index path.
|
|
3448
|
+
* @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
|
|
3449
|
+
* }
|
|
3450
|
+
* @return <BMCollectionViewLayoutAttributes, nullable> The corresponding cell attributes, or undefined if the section index is out of bounds
|
|
3451
|
+
* or the requested supplementary view type is unsupported.
|
|
3452
|
+
*/
|
|
3453
|
+
computedAttributesForSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
3454
|
+
var cache = (options && options.usingCache) || this.cachedLayout;
|
|
3455
|
+
var sectionIndex = options.atIndexPath.section;
|
|
3456
|
+
|
|
3457
|
+
if (sectionIndex >= cache.sections.length) return undefined;
|
|
3458
|
+
|
|
3459
|
+
var section = cache.sections[sectionIndex];
|
|
3460
|
+
var attributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath});
|
|
3461
|
+
|
|
3462
|
+
if (identifier === BMCollectionViewFlowLayoutSupplementaryView.Header) {
|
|
3463
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3464
|
+
attributes.frame = BMRectMake( cache.sectionInsets.left,
|
|
3465
|
+
section.top,
|
|
3466
|
+
this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
|
|
3467
|
+
cache.headerHeight);
|
|
3468
|
+
}
|
|
3469
|
+
else {
|
|
3470
|
+
attributes.frame = BMRectMake( section.left,
|
|
3471
|
+
cache.sectionInsets.top + cache.topPadding,
|
|
3472
|
+
cache.headderHeight,
|
|
3473
|
+
this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding);
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
return attributes;
|
|
3477
|
+
}
|
|
3478
|
+
else if (identifier === BMCollectionViewFlowLayoutSupplementaryView.Footer) {
|
|
3479
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3480
|
+
attributes.frame = BMRectMake( cache.sectionInsets.left,
|
|
3481
|
+
section.bottom - cache.footerHeight,
|
|
3482
|
+
this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
|
|
3483
|
+
cache.footerHeight);
|
|
3484
|
+
}
|
|
3485
|
+
else {
|
|
3486
|
+
attributes.frame = BMRectMake( section.right - cache.footerHeight,
|
|
3487
|
+
cache.sectionInsets.top + cache.topPadding,
|
|
3488
|
+
cache.footerHeight,
|
|
3489
|
+
this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding);
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
return attributes;
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
// Other types of supplementary views are not supported.
|
|
3496
|
+
return undefined;
|
|
3497
|
+
},
|
|
3498
|
+
|
|
3499
|
+
|
|
3500
|
+
// @override
|
|
3501
|
+
attributesForElementsInRect: function (rect) {
|
|
3502
|
+
// When using automatic cell sizes, it is important to ensure that the sizing information has been computed
|
|
3503
|
+
// for the entire requested rexct
|
|
3504
|
+
if (this._expectedCellSize) {
|
|
3505
|
+
if (this._orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3506
|
+
if (this.cachedLayout.resolvedHeight < rect.bottom) {
|
|
3507
|
+
// If the layout has not been resolved for this rect, continue computing it until it has
|
|
3508
|
+
this._layoutIterator.next({rect: rect});
|
|
3509
|
+
|
|
3510
|
+
// Invalidate the layout size
|
|
3511
|
+
this.invalidateContentSize();
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
else {
|
|
3515
|
+
if (this.cachedLayout.resolvedWidth < rect.right) {
|
|
3516
|
+
// If the layout has not been resolved for this rect, continue computing it until it has
|
|
3517
|
+
this._layoutIterator.next({rect: rect});
|
|
3518
|
+
|
|
3519
|
+
// Invalidate the layout size
|
|
3520
|
+
this.invalidateContentSize();
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
// When there are no sections in the collection view, this method will always return a single instance of attributes
|
|
3526
|
+
// for the empty supplementary view
|
|
3527
|
+
if (this.collectionView.numberOfSections() == 0) {
|
|
3528
|
+
var attribute = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
3529
|
+
BMCollectionViewTableLayoutSupplementaryView.Empty,
|
|
3530
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([0])}
|
|
3531
|
+
);
|
|
3532
|
+
|
|
3533
|
+
attribute.frame = BMRectMake(0, 0, this.collectionView.frame.size.width, this.collectionView.frame.size.height);
|
|
3534
|
+
return [attribute];
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
if (!this._cellSize || this._expectedCellSize) return this.cachedAttributesForElementsInRect(rect);
|
|
3538
|
+
|
|
3539
|
+
return this.computedAttributesForElementsInRect(rect);
|
|
3540
|
+
|
|
3541
|
+
},
|
|
3542
|
+
|
|
3543
|
+
|
|
3544
|
+
/**
|
|
3545
|
+
* Returns the cached cell attributes for all elements in the given rect. This method should only be used when using variable cell sizes.
|
|
3546
|
+
* @param rect <BMRect> The rect.
|
|
3547
|
+
* {
|
|
3548
|
+
* @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
|
|
3549
|
+
* }
|
|
3550
|
+
* @return <[BMCollectionViewLayoutAttributes]> An array of cell attributes.
|
|
3551
|
+
*/
|
|
3552
|
+
cachedAttributesForElementsInRect: function (rect, options) {
|
|
3553
|
+
var cache = (options && options.usingCache) || this.cachedLayout;
|
|
3554
|
+
var attributes = [];
|
|
3555
|
+
|
|
3556
|
+
var rectTop = rect.origin.y;
|
|
3557
|
+
var rectBottom = rect.bottom;
|
|
3558
|
+
var rectLeft = rect.origin.x;
|
|
3559
|
+
var rectRight = rect.right;
|
|
3560
|
+
|
|
3561
|
+
var sectionIndex = 0;
|
|
3562
|
+
var sectionCount = this._expectedCellSize ? cache.sectionCount : cache.sections.length;
|
|
3563
|
+
var section;
|
|
3564
|
+
|
|
3565
|
+
// Find the section that intersects this rect's top origin
|
|
3566
|
+
for (; sectionIndex < sectionCount; sectionIndex++) {
|
|
3567
|
+
section = cache.sections[sectionIndex];
|
|
3568
|
+
|
|
3569
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3570
|
+
if (section.bottom > rectTop) break;
|
|
3571
|
+
}
|
|
3572
|
+
else {
|
|
3573
|
+
if (section.right > rectLeft) break;
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
// If there were no sections return nothing
|
|
3578
|
+
if (sectionIndex == sectionCount) return attributes;
|
|
3579
|
+
|
|
3580
|
+
// Find the first row index that intersects the rect's top.
|
|
3581
|
+
var rowIndex = -1;
|
|
3582
|
+
|
|
3583
|
+
// Check the header first
|
|
3584
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3585
|
+
if (!section.headerAttributes || section.headerAttributes.frame.bottom < rectTop) {
|
|
3586
|
+
|
|
3587
|
+
// If the header doesn't intersect the point, check each row
|
|
3588
|
+
rowIndex = 0;
|
|
3589
|
+
|
|
3590
|
+
var rows = section.rows;
|
|
3591
|
+
var rowsLength = rows.length;
|
|
3592
|
+
|
|
3593
|
+
for (var i = 0; i < rowsLength; i++) {
|
|
3594
|
+
if (rows[i].bottom > rectTop) {
|
|
3595
|
+
rowIndex = i;
|
|
3596
|
+
break;
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
// And finally, if no row intersects the point, the footer must intersect it
|
|
3601
|
+
rowIndex = i;
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
else {
|
|
3605
|
+
if (!section.headerAttributes || section.headerAttributes.frame.right < rectLeft) {
|
|
3606
|
+
|
|
3607
|
+
// If the header doesn't intersect the point, check each row
|
|
3608
|
+
rowIndex = 0;
|
|
3609
|
+
|
|
3610
|
+
var rows = section.rows;
|
|
3611
|
+
var rowsLength = rows.length;
|
|
3612
|
+
|
|
3613
|
+
for (var i = 0; i < rowsLength; i++) {
|
|
3614
|
+
if (rows[i].right > rectLeft) {
|
|
3615
|
+
rowIndex = i;
|
|
3616
|
+
break;
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
|
|
3620
|
+
// And finally, if no row intersects the point, the footer must intersect it
|
|
3621
|
+
rowIndex = i;
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
var topmostSection = section;
|
|
3626
|
+
var bottommostSection = section;
|
|
3627
|
+
var lastFooterAttributes, lastFooterAttributesSection;
|
|
3628
|
+
|
|
3629
|
+
// If there are no header attributes but this layout pins headers to content edges, add them now
|
|
3630
|
+
if (cache.pinsHeadersToContentEdge && rowIndex != -1) {
|
|
3631
|
+
var headerAttributes = section.headerAttributes.copy();
|
|
3632
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3633
|
+
headerAttributes.frame.origin.y = this.collectionView.scrollOffset.y;
|
|
3634
|
+
}
|
|
3635
|
+
else {
|
|
3636
|
+
headerAttributes.frame.origin.x = this.collectionView.scrollOffset.x;
|
|
3637
|
+
}
|
|
3638
|
+
attributes.push(headerAttributes);
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
// Start adding attributes until the current supplementary view or row's bottom edge is below the rect
|
|
3642
|
+
try {
|
|
3643
|
+
for (; sectionIndex < sectionCount; sectionIndex++) {
|
|
3644
|
+
var section = cache.sections[sectionIndex];
|
|
3645
|
+
// This can happen if the last added element is at the end of the section and the first unresolved element
|
|
3646
|
+
// is in the next section
|
|
3647
|
+
if (!section && this._expectedCellSize) return attributes;
|
|
3648
|
+
var rows = section.rows;
|
|
3649
|
+
var rowsLength = rows.length;
|
|
3650
|
+
|
|
3651
|
+
for (; rowIndex <= rowsLength; rowIndex++) {
|
|
3652
|
+
|
|
3653
|
+
if (rowIndex == -1) {
|
|
3654
|
+
// Headers
|
|
3655
|
+
if (section.headerAttributes) {
|
|
3656
|
+
var headerAttributes = section.headerAttributes;
|
|
3657
|
+
|
|
3658
|
+
// Pin the headers to the content edge if needed
|
|
3659
|
+
if (cache.pinsHeadersToContentEdge && section === topmostSection) {
|
|
3660
|
+
headerAttributes = headerAttributes.copy();
|
|
3661
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3662
|
+
headerAttributes.frame.origin.y = Math.max(this.collectionView.scrollOffset.y, headerAttributes.frame.origin.y);
|
|
3663
|
+
}
|
|
3664
|
+
else {
|
|
3665
|
+
headerAttributes.frame.origin.x = Math.max(this.collectionView.scrollOffset.x, headerAttributes.frame.origin.x);
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3670
|
+
var origin = headerAttributes.frame.origin.y;
|
|
3671
|
+
if (origin > rectBottom) return attributes;
|
|
3672
|
+
}
|
|
3673
|
+
else {
|
|
3674
|
+
var origin = headerAttributes.frame.origin.x;
|
|
3675
|
+
if (origin > rectRight) return attributes;
|
|
3676
|
+
}
|
|
3677
|
+
|
|
3678
|
+
attributes.push(headerAttributes);
|
|
3679
|
+
|
|
3680
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3681
|
+
var end = headerAttributes.frame.bottom;
|
|
3682
|
+
if (end > rectBottom) return attributes;
|
|
3683
|
+
}
|
|
3684
|
+
else {
|
|
3685
|
+
var end = headerAttributes.frame.right;
|
|
3686
|
+
if (end > rectRight) return attributes;
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
bottommostSection = section;
|
|
3691
|
+
}
|
|
3692
|
+
else if (rowIndex == rowsLength) {
|
|
3693
|
+
// Footers
|
|
3694
|
+
if (section.footerAttributes) {
|
|
3695
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3696
|
+
var origin = section.footerAttributes.frame.origin.y;
|
|
3697
|
+
if (origin > rectBottom) return attributes;
|
|
3698
|
+
}
|
|
3699
|
+
else {
|
|
3700
|
+
var origin = section.footerAttributes.frame.origin.x;
|
|
3701
|
+
if (origin > rectRight) return attributes;
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
var footerAttributes = section.footerAttributes;
|
|
3705
|
+
// When the footers are pinned, work on copies of the original attributes to not alter the cached layout
|
|
3706
|
+
// The actual pinning is performend in the final block
|
|
3707
|
+
if (this.collectionView._pinsFootersToContentEdge) {
|
|
3708
|
+
footerAttributes = footerAttributes.copy();
|
|
3709
|
+
lastFooterAttributes = footerAttributes;
|
|
3710
|
+
lastFooterAttributesSection = section;
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
attributes.push(footerAttributes);
|
|
3714
|
+
|
|
3715
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3716
|
+
var end = section.footerAttributes.frame.origin.y;
|
|
3717
|
+
if (end > rectBottom) return attributes;
|
|
3718
|
+
}
|
|
3719
|
+
else {
|
|
3720
|
+
var end = section.footerAttributes.frame.origin.x;
|
|
3721
|
+
if (end > rectRight) return attributes;
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
else {
|
|
3726
|
+
// Rows
|
|
3727
|
+
var row = rows[rowIndex];
|
|
3728
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3729
|
+
var origin = row.top;
|
|
3730
|
+
if (origin > rectBottom) return attributes;
|
|
3731
|
+
}
|
|
3732
|
+
else {
|
|
3733
|
+
var origin = row.left;
|
|
3734
|
+
if (origin > rectRight) return attributes;
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3737
|
+
for (var i = 0; i < row.attributes.length; i++) {
|
|
3738
|
+
attributes.push(row.attributes[i]);
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3742
|
+
var end = row.bottom;
|
|
3743
|
+
if (end > rectBottom) return attributes;
|
|
3744
|
+
}
|
|
3745
|
+
else {
|
|
3746
|
+
var end = row.right;
|
|
3747
|
+
if (end > rectRight) return attributes;
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
}
|
|
3752
|
+
|
|
3753
|
+
rowIndex = -1;
|
|
3754
|
+
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
finally {
|
|
3758
|
+
// Defer adjusting the bottommost section's footer attributes until after all the other attributes have been processed
|
|
3759
|
+
if (cache.pinsFootersToContentEdge && this._showsFooters) {
|
|
3760
|
+
var footerAttributes;
|
|
3761
|
+
|
|
3762
|
+
if (lastFooterAttributesSection === bottommostSection) {
|
|
3763
|
+
footerAttributes = lastFooterAttributes;
|
|
3764
|
+
}
|
|
3765
|
+
else {
|
|
3766
|
+
footerAttributes = bottommostSection.footerAttributes.copy();
|
|
3767
|
+
attributes.push(footerAttributes);
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3771
|
+
footerAttributes.frame.origin.y = Math.min(footerAttributes.frame.origin.y, this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - footerAttributes.frame.size.height);
|
|
3772
|
+
}
|
|
3773
|
+
else {
|
|
3774
|
+
footerAttributes.frame.origin.x = Math.min(footerAttributes.frame.origin.x, this.collectionView.scrollOffset.x + this.collectionView.frame.size.width - footerAttributes.frame.size.width);
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
|
|
3779
|
+
return attributes;
|
|
3780
|
+
},
|
|
3781
|
+
|
|
3782
|
+
|
|
3783
|
+
/**
|
|
3784
|
+
* Computes and returns the cell attributes for all elements in the given rect. This method should only be invoked when using fixed cell sizes.
|
|
3785
|
+
* @param rect <BMRect> The rect.
|
|
3786
|
+
* {
|
|
3787
|
+
* @param usingCache <Object, nullable> Defaults to this layout's current cache. The layout cache.
|
|
3788
|
+
* }
|
|
3789
|
+
* @return <[BMCollectionViewLayoutAttributes]> An array of cell attributes.
|
|
3790
|
+
*/
|
|
3791
|
+
computedAttributesForElementsInRect: function (rect, options) {
|
|
3792
|
+
var cache = (options && options.usingCache) || this.cachedLayout;
|
|
3793
|
+
var attributes = [];
|
|
3794
|
+
|
|
3795
|
+
/*attributes.push = item => {
|
|
3796
|
+
let frame = document.createElement('div');
|
|
3797
|
+
frame.style.cssText = `left: ${item.frame.origin.x}px; top: ${item.frame.origin.y}px; width: ${item.frame.size.width}px; height: ${item.frame.size.height}px; outline: 1px solid red;`;
|
|
3798
|
+
this.collectionView._contentWrapper[0].appendChild(frame);
|
|
3799
|
+
|
|
3800
|
+
return Array.prototype.push.apply(attributes, arguments);
|
|
3801
|
+
}*/
|
|
3802
|
+
|
|
3803
|
+
var rectTop = rect.origin.y;
|
|
3804
|
+
var rectBottom = rect.bottom;
|
|
3805
|
+
var rectLeft = rect.origin.x;
|
|
3806
|
+
var rectRight = rect.right;
|
|
3807
|
+
var rectEnd = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? rectBottom : rectRight;
|
|
3808
|
+
|
|
3809
|
+
var sectionIndex = 0;
|
|
3810
|
+
var sectionCount = cache.sections.length;
|
|
3811
|
+
var section;
|
|
3812
|
+
|
|
3813
|
+
// Find the section that intersects this rect's top origin
|
|
3814
|
+
for (; sectionIndex < sectionCount; sectionIndex++) {
|
|
3815
|
+
section = cache.sections[sectionIndex];
|
|
3816
|
+
|
|
3817
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3818
|
+
if (section.bottom > rectTop) break;
|
|
3819
|
+
}
|
|
3820
|
+
else {
|
|
3821
|
+
if (section.right > rectLeft) break;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
// If there were no sections return nothing
|
|
3826
|
+
if (sectionIndex == sectionCount) return attributes;
|
|
3827
|
+
|
|
3828
|
+
// Find the first row index that intersects the rect's top.
|
|
3829
|
+
var rowIndex = -1;
|
|
3830
|
+
|
|
3831
|
+
// The top is used as a caret that caches the previously rendered row or supplementary view's bottom position
|
|
3832
|
+
// and represents the top position at which the current row or supplementary view should be rendered
|
|
3833
|
+
var top = section.top;
|
|
3834
|
+
var left = section.left;
|
|
3835
|
+
var start = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? top : left;
|
|
3836
|
+
|
|
3837
|
+
// These control variables keep track of the topmost and bottommost sections in case it is required to pin headers or footers to the content edges
|
|
3838
|
+
var topmostSection = section;
|
|
3839
|
+
var bottommostSection = section;
|
|
3840
|
+
var lastFooterAttributes, lastFooterAttributesSection;
|
|
3841
|
+
|
|
3842
|
+
let sectionStartAttribute = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'top' : 'left';
|
|
3843
|
+
let originAttribute = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'y' : 'x';
|
|
3844
|
+
let supplementaryViewSizeAttribute = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? 'height' : 'width';
|
|
3845
|
+
let rectStart = cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical ? rectTop : rectLeft;
|
|
3846
|
+
|
|
3847
|
+
// Check the header first
|
|
3848
|
+
if (!cache.showsHeaders || section[sectionStartAttribute] + cache.headerHeight < rectStart) {
|
|
3849
|
+
|
|
3850
|
+
// If the header doesn't intersect the point, check each row
|
|
3851
|
+
rowIndex = 0;
|
|
3852
|
+
|
|
3853
|
+
var rowsLength = Math.ceil(section.numberOfObjects / cache.numberOfColumns) | 0;
|
|
3854
|
+
|
|
3855
|
+
start = cache.showsHeaders ? section[sectionStartAttribute] + cache.headerHeight + cache.rowSpacing : section[sectionStartAttribute];
|
|
3856
|
+
|
|
3857
|
+
// If no row advances past the rectTop, then rendering should begin at the first footer
|
|
3858
|
+
// In that case i will be equals to rowsLength, but the for condition will fail, which will not appropriately increase the rowIndex
|
|
3859
|
+
let caretIsAtRow = NO;
|
|
3860
|
+
for (var i = 0; i < rowsLength; i++) {
|
|
3861
|
+
rowIndex = i;
|
|
3862
|
+
|
|
3863
|
+
if (start + cache.rowBreadth > rectStart) {
|
|
3864
|
+
caretIsAtRow = YES;
|
|
3865
|
+
break;
|
|
3866
|
+
}
|
|
3867
|
+
else {
|
|
3868
|
+
start += cache.rowBreadth + cache.rowSpacing;
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
if (!caretIsAtRow) {
|
|
3874
|
+
rowIndex = i;
|
|
3875
|
+
}
|
|
3876
|
+
|
|
3877
|
+
// And finally, if no row intersects the point, the footer must intersect it
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
// If this layout pins headers to the content edge and the first index path is not this section's header, create its attributes
|
|
3881
|
+
// now and add it to the attributes array
|
|
3882
|
+
if (cache.pinsHeadersToContentEdge && rowIndex > -1 && cache.showsHeaders) {
|
|
3883
|
+
// Construct the attributes
|
|
3884
|
+
var headerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
3885
|
+
BMCollectionViewFlowLayoutSupplementaryView.Header,
|
|
3886
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([sectionIndex])}
|
|
3887
|
+
);
|
|
3888
|
+
|
|
3889
|
+
// The frame
|
|
3890
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3891
|
+
headerAttributes.frame = BMRectMake(
|
|
3892
|
+
cache.sectionInsets.left, this.collectionView.scrollOffset.y,
|
|
3893
|
+
this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
|
|
3894
|
+
cache.headerHeight
|
|
3895
|
+
);
|
|
3896
|
+
}
|
|
3897
|
+
else {
|
|
3898
|
+
headerAttributes.frame = BMRectMake(
|
|
3899
|
+
this.collectionView.scrollOffset.x, cache.sectionInsets.top + cache.topPadding,
|
|
3900
|
+
cache.headerHeight,
|
|
3901
|
+
this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
|
|
3902
|
+
);
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
// Float the header above the content
|
|
3906
|
+
headerAttributes.style.zIndex = 1;
|
|
3907
|
+
|
|
3908
|
+
attributes.push(headerAttributes);
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3911
|
+
|
|
3912
|
+
var numberOfColumns = cache.numberOfColumns;
|
|
3913
|
+
|
|
3914
|
+
// Start adding attributes until the current supplementary view or row's top origin is below the rect
|
|
3915
|
+
try {
|
|
3916
|
+
for (; sectionIndex < sectionCount; sectionIndex++) {
|
|
3917
|
+
var section = cache.sections[sectionIndex];
|
|
3918
|
+
var rowsLength = Math.ceil(section.numberOfObjects / numberOfColumns) | 0;
|
|
3919
|
+
|
|
3920
|
+
for (; rowIndex <= rowsLength; rowIndex++) {
|
|
3921
|
+
|
|
3922
|
+
if (rowIndex == -1) {
|
|
3923
|
+
// Headers
|
|
3924
|
+
if (cache.showsHeaders) {
|
|
3925
|
+
if (start > rectEnd) return attributes;
|
|
3926
|
+
|
|
3927
|
+
// Construct the attributes
|
|
3928
|
+
var headerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
3929
|
+
BMCollectionViewFlowLayoutSupplementaryView.Header,
|
|
3930
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([sectionIndex])}
|
|
3931
|
+
);
|
|
3932
|
+
|
|
3933
|
+
// The frame
|
|
3934
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3935
|
+
headerAttributes.frame = BMRectMake(
|
|
3936
|
+
cache.sectionInsets.left, start,
|
|
3937
|
+
this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
|
|
3938
|
+
cache.headerHeight
|
|
3939
|
+
);
|
|
3940
|
+
}
|
|
3941
|
+
else {
|
|
3942
|
+
headerAttributes.frame = BMRectMake(
|
|
3943
|
+
start, cache.sectionInsets.top + cache.topPadding,
|
|
3944
|
+
cache.headerHeight,
|
|
3945
|
+
this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
|
|
3946
|
+
);
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
// Adjust the attributes for pinning if needed
|
|
3950
|
+
if (section === topmostSection && cache.pinsHeadersToContentEdge) {
|
|
3951
|
+
|
|
3952
|
+
// If the header's Y origin is lower than the content edge, the topmost section is an incoming section
|
|
3953
|
+
// and the previous section's header should be rendered as outgoing
|
|
3954
|
+
if (headerAttributes.frame.origin[originAttribute] > this.collectionView.scrollOffset[originAttribute]) {
|
|
3955
|
+
var previousSection = cache.sections[sectionIndex - 1];
|
|
3956
|
+
if (previousSection) {
|
|
3957
|
+
var previousHeaderAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
3958
|
+
BMCollectionViewFlowLayoutSupplementaryView.Header,
|
|
3959
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([sectionIndex - 1])}
|
|
3960
|
+
);
|
|
3961
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
3962
|
+
previousHeaderAttributes.frame = BMRectMake(
|
|
3963
|
+
cache.sectionInsets.left,
|
|
3964
|
+
Math.min(this.collectionView.scrollOffset.y, headerAttributes.frame.origin.y - cache.headerHeight),
|
|
3965
|
+
this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
|
|
3966
|
+
cache.headerHeight
|
|
3967
|
+
);
|
|
3968
|
+
}
|
|
3969
|
+
else {
|
|
3970
|
+
previousHeaderAttributes.frame = BMRectMake(
|
|
3971
|
+
Math.min(this.collectionView.scrollOffset[originAttribute], headerAttributes.frame.origin[originAttribute] - cache.headerHeight),
|
|
3972
|
+
cache.sectionInsets.top + cache.topPadding,
|
|
3973
|
+
cache.headerHeight,
|
|
3974
|
+
this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
|
|
3975
|
+
);
|
|
3976
|
+
}
|
|
3977
|
+
attributes.push(previousHeaderAttributes);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3981
|
+
headerAttributes.frame.origin[originAttribute] = Math.max(headerAttributes.frame.origin[originAttribute], this.collectionView.scrollOffset[originAttribute]);
|
|
3982
|
+
|
|
3983
|
+
// Float the header above the content
|
|
3984
|
+
headerAttributes.style.zIndex = 1;
|
|
3985
|
+
}
|
|
3986
|
+
|
|
3987
|
+
// Advance the caret
|
|
3988
|
+
start += cache.headerHeight + cache.rowSpacing;
|
|
3989
|
+
|
|
3990
|
+
attributes.push(headerAttributes);
|
|
3991
|
+
}
|
|
3992
|
+
|
|
3993
|
+
bottommostSection = section;
|
|
3994
|
+
}
|
|
3995
|
+
else if (rowIndex == rowsLength) {
|
|
3996
|
+
// Footers
|
|
3997
|
+
if (cache.showsFooters) {
|
|
3998
|
+
if (start > rectEnd) return attributes;
|
|
3999
|
+
|
|
4000
|
+
var footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
4001
|
+
BMCollectionViewFlowLayoutSupplementaryView.Footer,
|
|
4002
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([sectionIndex])}
|
|
4003
|
+
);
|
|
4004
|
+
|
|
4005
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
4006
|
+
footerAttributes.frame = BMRectMake(
|
|
4007
|
+
cache.sectionInsets.left, start,
|
|
4008
|
+
this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
|
|
4009
|
+
cache.footerHeight
|
|
4010
|
+
);
|
|
4011
|
+
}
|
|
4012
|
+
else {
|
|
4013
|
+
footerAttributes.frame = BMRectMake(
|
|
4014
|
+
start, cache.sectionInsets.top + cache.topPadding,
|
|
4015
|
+
cache.footerHeight,
|
|
4016
|
+
this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
|
|
4017
|
+
);
|
|
4018
|
+
}
|
|
4019
|
+
|
|
4020
|
+
// Retain these attributes and their corresponding section in case they need to be adjusted for pinning afterwards
|
|
4021
|
+
lastFooterAttributes = footerAttributes;
|
|
4022
|
+
lastFooterAttributesSection = section;
|
|
4023
|
+
|
|
4024
|
+
// Because the section ends after the footer, the caret must move past the section insets to the next section
|
|
4025
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
4026
|
+
start += cache.footerHeight + cache.sectionInsets.top + cache.sectionInsets.bottom;
|
|
4027
|
+
}
|
|
4028
|
+
else {
|
|
4029
|
+
start += cache.footerHeight + cache.sectionInsets.left + cache.sectionInsets.right;
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
attributes.push(footerAttributes);
|
|
4033
|
+
}
|
|
4034
|
+
else {
|
|
4035
|
+
// If there is no footer, the caret must move past the section insets to the next section
|
|
4036
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
4037
|
+
start += cache.sectionInsets.top + cache.sectionInsets.bottom;
|
|
4038
|
+
}
|
|
4039
|
+
else {
|
|
4040
|
+
start += cache.sectionInsets.left + cache.sectionInsets.right;
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
if (start > rectEnd) return attributes;
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
else {
|
|
4047
|
+
// Rows
|
|
4048
|
+
if (start > rectEnd) return attributes;
|
|
4049
|
+
|
|
4050
|
+
var startingObjectIndex = rowIndex * numberOfColumns;
|
|
4051
|
+
var prototypeRow;
|
|
4052
|
+
var numberOfColumnsInThisRow = numberOfColumns;
|
|
4053
|
+
|
|
4054
|
+
if (rowIndex == rowsLength - 1) {
|
|
4055
|
+
// For the last row determine the prototype row. When there is an exact match between the last row's column count and
|
|
4056
|
+
// other row's column count, the remainder will be 0. In that case the full number of columns should be assumed.
|
|
4057
|
+
numberOfColumnsInThisRow = (section.numberOfObjects % numberOfColumns) || numberOfColumns;
|
|
4058
|
+
prototypeRow = cache.prototypeRows[numberOfColumnsInThisRow - 1];
|
|
4059
|
+
}
|
|
4060
|
+
else {
|
|
4061
|
+
// All other rows use the prototype for the maximum column count
|
|
4062
|
+
prototypeRow = cache.prototypeRows[numberOfColumns - 1];
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4065
|
+
// Construct the attributes for each cell in this row, using the prototype row as a starting point
|
|
4066
|
+
for (var i = 0; i < numberOfColumnsInThisRow; i++) {
|
|
4067
|
+
var objectIndex = startingObjectIndex + i;
|
|
4068
|
+
|
|
4069
|
+
var cellAttributes = BMCollectionViewLayoutAttributesMakeForCellAtIndexPath(this.collectionView.indexPathForObjectAtRow(objectIndex, {inSectionAtIndex: sectionIndex}));
|
|
4070
|
+
|
|
4071
|
+
cellAttributes.frame = prototypeRow.attributes[i].frame.copy();
|
|
4072
|
+
cellAttributes.frame.origin[originAttribute] = start;
|
|
4073
|
+
|
|
4074
|
+
attributes.push(cellAttributes);
|
|
4075
|
+
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
// Advance the caret
|
|
4079
|
+
start += cache.rowBreadth;
|
|
4080
|
+
|
|
4081
|
+
// Add the spacing between the rows
|
|
4082
|
+
// If this is the last row and the layout doesn't use footers, don't add the spacing as this row is the bottom edge of the section
|
|
4083
|
+
if (cache.showsFooters || rowIndex < rowsLength - 1) {
|
|
4084
|
+
start += cache.rowSpacing;
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
}
|
|
4090
|
+
|
|
4091
|
+
rowIndex = -1;
|
|
4092
|
+
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
finally {
|
|
4096
|
+
// Defer adjusting the footer attributes until after all other attributes have been processed
|
|
4097
|
+
if (cache.pinsFootersToContentEdge && cache.showsFooters && cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
4098
|
+
var footerAttributes;
|
|
4099
|
+
|
|
4100
|
+
if (lastFooterAttributesSection === bottommostSection) {
|
|
4101
|
+
footerAttributes = lastFooterAttributes;
|
|
4102
|
+
|
|
4103
|
+
// If the footer's Y origin is higher than the content edge, the bottommost section is an outgoing section
|
|
4104
|
+
// and the next section's footer should be rendered as incoming
|
|
4105
|
+
if (footerAttributes.frame.origin[originAttribute] < this.collectionView.scrollOffset[originAttribute] + this.collectionView.frame.size[supplementaryViewSizeAttribute] - cache.footerHeight) {
|
|
4106
|
+
var nextSection = cache.sections[footerAttributes.indexPath.section + 1];
|
|
4107
|
+
if (nextSection) {
|
|
4108
|
+
var nextFooterAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
4109
|
+
BMCollectionViewFlowLayoutSupplementaryView.Footer,
|
|
4110
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([footerAttributes.indexPath.section + 1])}
|
|
4111
|
+
);
|
|
4112
|
+
|
|
4113
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
4114
|
+
nextFooterAttributes.frame = BMRectMake(
|
|
4115
|
+
cache.sectionInsets.left,
|
|
4116
|
+
Math.max(this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - cache.footerHeight, footerAttributes.frame.bottom),
|
|
4117
|
+
this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
|
|
4118
|
+
cache.footerHeight
|
|
4119
|
+
);
|
|
4120
|
+
}
|
|
4121
|
+
else {
|
|
4122
|
+
nextFooterAttributes.frame = BMRectMake(
|
|
4123
|
+
Math.max(this.collectionView.scrollOffset.x + this.collectionView.frame.size.width - cache.footerHeight, footerAttributes.frame.right),
|
|
4124
|
+
cache.sectionInsets.top + cache.topPadding,
|
|
4125
|
+
cache.footerHeight,
|
|
4126
|
+
this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
|
|
4127
|
+
);
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
attributes.push(nextFooterAttributes);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
footerAttributes.frame.origin[originAttribute] = Math.min(footerAttributes.frame.origin[originAttribute], this.collectionView.scrollOffset[originAttribute] + this.collectionView.frame.size[supplementaryViewSizeAttribute] - footerAttributes.frame.size[supplementaryViewSizeAttribute]);
|
|
4135
|
+
|
|
4136
|
+
// Float this footer above all other content
|
|
4137
|
+
footerAttributes.style.zIndex = 1;
|
|
4138
|
+
}
|
|
4139
|
+
else {
|
|
4140
|
+
// Construct the footer attributes if they weren't already constructed
|
|
4141
|
+
footerAttributes = BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
4142
|
+
BMCollectionViewFlowLayoutSupplementaryView.Footer,
|
|
4143
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([sectionIndex])}
|
|
4144
|
+
);
|
|
4145
|
+
|
|
4146
|
+
if (cache.orientation == BMCollectionViewFlowLayoutOrientation.Vertical) {
|
|
4147
|
+
footerAttributes.frame = BMRectMake(
|
|
4148
|
+
cache.sectionInsets.left, this.collectionView.scrollOffset.y + this.collectionView.frame.size.height - cache.footerHeight,
|
|
4149
|
+
this.collectionView.frame.size.width - cache.sectionInsets.left - cache.sectionInsets.right,
|
|
4150
|
+
cache.footerHeight
|
|
4151
|
+
);
|
|
4152
|
+
}
|
|
4153
|
+
else {
|
|
4154
|
+
footerAttributes.frame = BMRectMake(
|
|
4155
|
+
this.collectionView.scrollOffset.x + this.collectionView.frame.size.width - cache.footerHeight,
|
|
4156
|
+
cache.sectionInsets.top + cache.topPadding,
|
|
4157
|
+
cache.footerHeight,
|
|
4158
|
+
this.collectionView.frame.size.height - cache.sectionInsets.top - cache.sectionInsets.bottom - cache.topPadding - cache.bottomPadding
|
|
4159
|
+
);
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
// Float this footer above all other content
|
|
4163
|
+
footerAttributes.style.zIndex = 1;
|
|
4164
|
+
|
|
4165
|
+
attributes.push(footerAttributes);
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4168
|
+
}
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
return attributes;
|
|
4172
|
+
},
|
|
4173
|
+
|
|
4174
|
+
// NOTE: All initialAttributes... methods will simply return a copy of the regular attributes with an additional style added
|
|
4175
|
+
|
|
4176
|
+
// @override - BMCollectionViewLayout
|
|
4177
|
+
initialAttributesForPresentedCellAtIndexPath: function (indexPath) {
|
|
4178
|
+
// Because cached layouts reuse the attributes, they must be copied before being modified
|
|
4179
|
+
var attributes = this.attributesForCellAtIndexPath(indexPath).copy();
|
|
4180
|
+
|
|
4181
|
+
if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForPresentedCellAtIndexPath) {
|
|
4182
|
+
return this.collectionView.delegate.collectionViewInitialAttributesForPresentedCellAtIndexPath(this.collectionView, indexPath, {withTargetAttributes: attributes});
|
|
4183
|
+
}
|
|
4184
|
+
|
|
4185
|
+
attributes.frame.origin.y += this.collectionView.frame.size.height / 2;
|
|
4186
|
+
|
|
4187
|
+
attributes.style = {
|
|
4188
|
+
rotateZ: '15deg',
|
|
4189
|
+
opacity: 0,
|
|
4190
|
+
translateZ: 0
|
|
4191
|
+
};
|
|
4192
|
+
|
|
4193
|
+
return attributes;
|
|
4194
|
+
},
|
|
4195
|
+
|
|
4196
|
+
// @override - BMCollectionViewLayout
|
|
4197
|
+
initialAttributesForPresentedSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
4198
|
+
// Because cached layouts reuse the attributes, they must be copied before being modified
|
|
4199
|
+
var attributes = this.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath}).copy();
|
|
4200
|
+
|
|
4201
|
+
if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForPresentedSupplementaryViewWithIdentifier) {
|
|
4202
|
+
return this.collectionView.delegate.collectionViewInitialAttributesForPresentedSupplementaryViewWithIdentifier(this.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
|
|
4203
|
+
}
|
|
4204
|
+
|
|
4205
|
+
attributes.frame.origin.x += attributes.frame.size.width / 2;
|
|
4206
|
+
|
|
4207
|
+
attributes.style = {
|
|
4208
|
+
opacity: 0,
|
|
4209
|
+
translateZ: 0
|
|
4210
|
+
};
|
|
4211
|
+
|
|
4212
|
+
return attributes;
|
|
4213
|
+
},
|
|
4214
|
+
|
|
4215
|
+
// @override - BMCollectionViewLayout
|
|
4216
|
+
initialAttributesForAppearingCellAtIndexPath: function (indexPath) {
|
|
4217
|
+
// Because cached layouts reuse the attributes, they must be copied before being modified
|
|
4218
|
+
var attributes = this.attributesForCellAtIndexPath(indexPath).copy();
|
|
4219
|
+
|
|
4220
|
+
if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForAppearingCellAtIndexPath) {
|
|
4221
|
+
return this.collectionView.delegate.collectionViewInitialAttributesForAppearingCellAtIndexPath(this.collectionView, indexPath, {withTargetAttributes: attributes});
|
|
4222
|
+
}
|
|
4223
|
+
|
|
4224
|
+
attributes.style = {opacity: 0, translateZ: 0, scaleX: 0.5, scaleY: 0.5};
|
|
4225
|
+
|
|
4226
|
+
return attributes;
|
|
4227
|
+
|
|
4228
|
+
},
|
|
4229
|
+
|
|
4230
|
+
// @override - BMCollectionViewLayout
|
|
4231
|
+
initialAttributesForAppearingSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
4232
|
+
// Because cached layouts reuse the attributes, they must be copied before being modified
|
|
4233
|
+
var attributes = this.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath}).copy();
|
|
4234
|
+
|
|
4235
|
+
if (this.collectionView.delegate && this.collectionView.delegate.collectionViewInitialAttributesForAppearingSupplementaryViewWithIdentifier) {
|
|
4236
|
+
return this.collectionView.delegate.collectionViewInitialAttributesForAppearingSupplementaryViewWithIdentifier(this.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
attributes.style = {opacity: 0, translateZ: 0, scaleX: 0.5, scaleY: 0.5};
|
|
4240
|
+
|
|
4241
|
+
return attributes;
|
|
4242
|
+
|
|
4243
|
+
},
|
|
4244
|
+
|
|
4245
|
+
// @override - BMCollectionViewLayout
|
|
4246
|
+
initialAttributesForMovingCellFromIndexPath: function (indexPath, options) {
|
|
4247
|
+
var attributes;
|
|
4248
|
+
var self = this;
|
|
4249
|
+
|
|
4250
|
+
this.collectionView.usingOldDataSet(function () {
|
|
4251
|
+
// When using automatic cell sizes, it is important to ensure that the sizing information has been computed
|
|
4252
|
+
// for the requested index path
|
|
4253
|
+
if (self._expectedCellSize) {
|
|
4254
|
+
if (self.previousLayout.resolvedIndexPath.section < indexPath.section ||
|
|
4255
|
+
(self.previousLayout.resolvedIndexPath.section == indexPath.section && self.previousLayout.resolvedIndexPath.row < indexPath.row)) {
|
|
4256
|
+
// If the layout has not been resolved up to this index path, continue computing it until it has
|
|
4257
|
+
self.previousLayout.iterator.next({indexPath: indexPath});
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4261
|
+
if (self._cellSize && !self._expectedCellSize) {
|
|
4262
|
+
attributes = self.computedAttributesForCellAtIndexPath(indexPath, {usingCache: self.previousLayout});
|
|
4263
|
+
}
|
|
4264
|
+
else {
|
|
4265
|
+
attributes = self.cachedAttributesForCellAtIndexPath(indexPath, {usingCache: self.previousLayout});
|
|
4266
|
+
}
|
|
4267
|
+
});
|
|
4268
|
+
|
|
4269
|
+
return attributes;
|
|
4270
|
+
},
|
|
4271
|
+
|
|
4272
|
+
// @override - BMCollectionViewLayout
|
|
4273
|
+
initialAttributesForMovingSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
4274
|
+
var attributes;
|
|
4275
|
+
var self = this;
|
|
4276
|
+
|
|
4277
|
+
this.collectionView.usingOldDataSet(function () {
|
|
4278
|
+
if (identifier === BMCollectionViewTableLayoutSupplementaryView.Empty) {
|
|
4279
|
+
attributes = self.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath});
|
|
4280
|
+
}
|
|
4281
|
+
else if (self._cellSize && !self._expectedCellSize) {
|
|
4282
|
+
attributes = self.computedAttributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath, usingCache: self.previousLayout});
|
|
4283
|
+
}
|
|
4284
|
+
else {
|
|
4285
|
+
attributes = self.cachedAttributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath, usingCache: self.previousLayout});
|
|
4286
|
+
}
|
|
4287
|
+
});
|
|
4288
|
+
|
|
4289
|
+
return attributes;
|
|
4290
|
+
},
|
|
4291
|
+
|
|
4292
|
+
// @override - BMCollectionViewLayout
|
|
4293
|
+
supplementaryViewsToInsert: function () {
|
|
4294
|
+
var count = this.collectionView.numberOfSections();
|
|
4295
|
+
var oldCount;
|
|
4296
|
+
var self = this;
|
|
4297
|
+
|
|
4298
|
+
this.collectionView.usingOldDataSet(function () {
|
|
4299
|
+
oldCount = self.collectionView.numberOfSections();
|
|
4300
|
+
});
|
|
4301
|
+
|
|
4302
|
+
var views = [];
|
|
4303
|
+
|
|
4304
|
+
for (var i = oldCount; i < count; i++) {
|
|
4305
|
+
if (this.showsHeaders) {
|
|
4306
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
4307
|
+
BMCollectionViewFlowLayoutSupplementaryView.Header,
|
|
4308
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
4309
|
+
));
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
if (this.showsFooters) {
|
|
4313
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
4314
|
+
BMCollectionViewFlowLayoutSupplementaryView.Footer,
|
|
4315
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
4316
|
+
));
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
if (oldCount > 0 && count == 0) {
|
|
4321
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
4322
|
+
BMCollectionViewTableLayoutSupplementaryView.Empty,
|
|
4323
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([0])}
|
|
4324
|
+
));
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
return views;
|
|
4328
|
+
},
|
|
4329
|
+
|
|
4330
|
+
// @override - BMCollectionViewLayout
|
|
4331
|
+
supplementaryViewsToDelete: function () {
|
|
4332
|
+
var count = this.collectionView.numberOfSections();
|
|
4333
|
+
var oldCount;
|
|
4334
|
+
var self = this;
|
|
4335
|
+
|
|
4336
|
+
this.collectionView.usingOldDataSet(function () {
|
|
4337
|
+
oldCount = self.collectionView.numberOfSections();
|
|
4338
|
+
});
|
|
4339
|
+
|
|
4340
|
+
var views = [];
|
|
4341
|
+
|
|
4342
|
+
for (var i = count; i < oldCount; i++) {
|
|
4343
|
+
if (this.showsHeaders) {
|
|
4344
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
4345
|
+
BMCollectionViewFlowLayoutSupplementaryView.Header,
|
|
4346
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
4347
|
+
));
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
if (this.showsFooters) {
|
|
4351
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
4352
|
+
BMCollectionViewFlowLayoutSupplementaryView.Footer,
|
|
4353
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([i])}
|
|
4354
|
+
));
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
|
|
4358
|
+
if (oldCount == 0 && count > 0) {
|
|
4359
|
+
views.push(BMCollectionViewLayoutAttributesMakeForSupplementaryViewWithIdentifier(
|
|
4360
|
+
BMCollectionViewFlowLayoutSupplementaryView.Empty,
|
|
4361
|
+
{atIndexPath: BMIndexPathMakeWithIndexes([0])}
|
|
4362
|
+
));
|
|
4363
|
+
}
|
|
4364
|
+
|
|
4365
|
+
return views;
|
|
4366
|
+
},
|
|
4367
|
+
|
|
4368
|
+
// NOTE: All finalAttributes... methods will simply return a copy of the regular attributes from the previous layout with an additional style added
|
|
4369
|
+
|
|
4370
|
+
// @override - BMCollectionViewLayout
|
|
4371
|
+
finalAttributesForDisappearingCellAtIndexPath: function (indexPath) {
|
|
4372
|
+
var attributes;
|
|
4373
|
+
var self = this;
|
|
4374
|
+
|
|
4375
|
+
this.collectionView.usingOldDataSet(function () {
|
|
4376
|
+
if (self._cellSize && !self._expectedCellSize) {
|
|
4377
|
+
attributes = self.computedAttributesForCellAtIndexPath(indexPath, {usingCache: self.previousLayout});
|
|
4378
|
+
}
|
|
4379
|
+
else {
|
|
4380
|
+
attributes = self.cachedAttributesForCellAtIndexPath(indexPath, {usingCache: self.previousLayout}).copy();
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4383
|
+
|
|
4384
|
+
if (self.collectionView.delegate && self.collectionView.delegate.collectionViewFinalAttributesForDisappearingCellAtIndexPath) {
|
|
4385
|
+
attributes = self.collectionView.delegate.collectionViewFinalAttributesForDisappearingCellAtIndexPath(self.collectionView, indexPath, {withTargetAttributes: attributes});
|
|
4386
|
+
}
|
|
4387
|
+
else {
|
|
4388
|
+
attributes.style = {opacity: 0, translateZ: 0, scaleX: 0.5, scaleY: 0.5};
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4391
|
+
});
|
|
4392
|
+
|
|
4393
|
+
return attributes;
|
|
4394
|
+
},
|
|
4395
|
+
|
|
4396
|
+
// @override - BMCollectionViewLayout
|
|
4397
|
+
finalAttributesForDisappearingSupplementaryViewWithIdentifier: function (identifier, options) {
|
|
4398
|
+
var attributes;
|
|
4399
|
+
var self = this;
|
|
4400
|
+
|
|
4401
|
+
this.collectionView.usingOldDataSet(function () {
|
|
4402
|
+
if (identifier === BMCollectionViewTableLayoutSupplementaryView.Empty) {
|
|
4403
|
+
attributes = self.attributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath});
|
|
4404
|
+
}
|
|
4405
|
+
else if (self._cellSize && !self._expectedCellSize) {
|
|
4406
|
+
attributes = self.computedAttributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath, usingCache: self.previousLayout});
|
|
4407
|
+
}
|
|
4408
|
+
else {
|
|
4409
|
+
attributes = self.cachedAttributesForSupplementaryViewWithIdentifier(identifier, {atIndexPath: options.atIndexPath, usingCache: self.previousLayout});
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4412
|
+
if (self.collectionView.delegate && self.collectionView.delegate.collectionViewFinalAttributesForDisappearingSupplementaryViewWithIdentifier) {
|
|
4413
|
+
attributes = self.collectionView.delegate.collectionViewFinalAttributesForDisappearingSupplementaryViewWithIdentifier(self.collectionView, identifier, {atIndexPath: options.atIndexPath, withTargetAttributes: attributes});
|
|
4414
|
+
}
|
|
4415
|
+
else {
|
|
4416
|
+
attributes.style = {opacity: 0, translateZ: 0, scaleX: 0.5, scaleY: 0.5};
|
|
4417
|
+
}
|
|
4418
|
+
|
|
4419
|
+
});
|
|
4420
|
+
|
|
4421
|
+
return attributes;
|
|
4422
|
+
},
|
|
4423
|
+
|
|
4424
|
+
// @override - BMCollectionViewLayout
|
|
4425
|
+
copy: function () {
|
|
4426
|
+
var copy = new BMCollectionViewFlowLayout();
|
|
4427
|
+
|
|
4428
|
+
copy.cellSize = this.cellSize && this.cellSize.copy();
|
|
4429
|
+
copy.rowSpacing = this.rowSpacing;
|
|
4430
|
+
|
|
4431
|
+
copy.showsHeaders = this.showsHeaders;
|
|
4432
|
+
copy.headerHeight = this.headerHeight;
|
|
4433
|
+
|
|
4434
|
+
copy.showsFooters = this.showsFooters;
|
|
4435
|
+
copy.footerHeight = this.footerHeight;
|
|
4436
|
+
|
|
4437
|
+
copy.sectionInsets = this.sectionInsets;
|
|
4438
|
+
|
|
4439
|
+
copy.gravity = this.gravity;
|
|
4440
|
+
copy.leftAlignFinalRow = this.leftAlignFinalRow;
|
|
4441
|
+
copy.alignment = this.alignment;
|
|
4442
|
+
copy.orientation = this.orientation;
|
|
4443
|
+
|
|
4444
|
+
copy.topPadding = this.topPadding;
|
|
4445
|
+
copy.bottomPadding = this.bottomPadding;
|
|
4446
|
+
copy.contentGravity = this.contentGravity;
|
|
4447
|
+
|
|
4448
|
+
copy.pinsHeadersToContentEdge = this.pinsHeadersToContentEdge;
|
|
4449
|
+
copy.pinsFootersToContentEdge = this.pinsFootersToContentEdge;
|
|
4450
|
+
|
|
4451
|
+
return copy;
|
|
4452
|
+
}
|
|
4453
|
+
|
|
4454
|
+
|
|
4455
|
+
|
|
4456
|
+
});
|
|
4457
|
+
|
|
4458
|
+
/**
|
|
4459
|
+
* Constructs and returns a new flow layout.
|
|
4460
|
+
* @return <BMCollectionViewFlowLayout> A flow layout.
|
|
4461
|
+
*/
|
|
4462
|
+
BMCollectionViewFlowLayout.flowLayout = function () {
|
|
4463
|
+
return new BMCollectionViewFlowLayout();
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4466
|
+
// @endtype
|
|
4467
|
+
|