@nativescript-community/ui-collectionview-alignedflowlayout 5.3.44
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 +461 -0
- package/LICENSE +201 -0
- package/README.md +173 -0
- package/index.android.d.ts +1 -0
- package/index.android.js +2 -0
- package/index.android.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.ios.d.ts +1 -0
- package/index.ios.js +84 -0
- package/index.ios.js.map +1 -0
- package/package.json +45 -0
- package/platforms/ios/src/AlignedCollectionViewFlowLayout.swift +494 -0
- package/typings/objc!AlignedCollectionViewFlowLayout.d.ts +37 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
//
|
|
2
|
+
// AlignedCollectionViewFlowLayout.swift
|
|
3
|
+
//
|
|
4
|
+
// Created by Mischa Hildebrand on 12/04/2017.
|
|
5
|
+
// Copyright © 2017 Mischa Hildebrand.
|
|
6
|
+
//
|
|
7
|
+
// Licensed under the terms of the MIT license:
|
|
8
|
+
//
|
|
9
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
// in the Software without restriction, including without limitation the rights
|
|
12
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
// furnished to do so, subject to the following conditions:
|
|
15
|
+
//
|
|
16
|
+
// The above copyright notice and this permission notice shall be included in
|
|
17
|
+
// all copies or substantial portions of the Software.
|
|
18
|
+
//
|
|
19
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
25
|
+
// THE SOFTWARE.
|
|
26
|
+
//
|
|
27
|
+
|
|
28
|
+
import UIKit
|
|
29
|
+
import Foundation
|
|
30
|
+
|
|
31
|
+
// MARK: - 🦆 Type definitions
|
|
32
|
+
|
|
33
|
+
/// An abstract protocol that defines an alignment.
|
|
34
|
+
protocol Alignment {}
|
|
35
|
+
|
|
36
|
+
/// Defines a horizontal alignment for UI elements.
|
|
37
|
+
@objc public enum HorizontalAlignment: Int, Alignment {
|
|
38
|
+
case left
|
|
39
|
+
case right
|
|
40
|
+
case leading
|
|
41
|
+
case trailing
|
|
42
|
+
case justified
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Defines a vertical alignment for UI elements.
|
|
46
|
+
@objc public enum VerticalAlignment: Int, Alignment {
|
|
47
|
+
case top
|
|
48
|
+
case center
|
|
49
|
+
case bottom
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// A horizontal alignment used internally by `AlignedCollectionViewFlowLayout`
|
|
53
|
+
/// to layout the items, after resolving layout direction specifics.
|
|
54
|
+
private enum EffectiveHorizontalAlignment: Alignment {
|
|
55
|
+
case left
|
|
56
|
+
case right
|
|
57
|
+
case justified
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Describes an axis with respect to which items can be aligned.
|
|
61
|
+
private struct AlignmentAxis<A: Alignment> {
|
|
62
|
+
|
|
63
|
+
/// Determines how items are aligned relative to the axis.
|
|
64
|
+
let alignment: A
|
|
65
|
+
|
|
66
|
+
/// Defines the position of the axis.
|
|
67
|
+
/// * If the `Alignment` is horizontal, the alignment axis is vertical and this is the position on the `x` axis.
|
|
68
|
+
/// * If the `Alignment` is vertical, the alignment axis is horizontal and this is the position on the `y` axis.
|
|
69
|
+
let position: CGFloat
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// MARK: - Flow Layout
|
|
75
|
+
|
|
76
|
+
/// A `UICollectionViewFlowLayout` subclass that gives you control
|
|
77
|
+
/// over the horizontal and vertical alignment of the cells.
|
|
78
|
+
/// You can use it to align the cells like words in a left- or right-aligned text
|
|
79
|
+
/// and you can specify how the cells are vertically aligned in their row.
|
|
80
|
+
@objcMembers
|
|
81
|
+
@objc(AlignedCollectionViewFlowLayout)
|
|
82
|
+
open class AlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
|
|
83
|
+
|
|
84
|
+
// MARK: - 🔶 Properties
|
|
85
|
+
|
|
86
|
+
/// Determines how the cells are horizontally aligned in a row.
|
|
87
|
+
/// - Note: The default is `.justified`.
|
|
88
|
+
public var horizontalAlignment: HorizontalAlignment = .justified
|
|
89
|
+
|
|
90
|
+
/// Determines how the cells are vertically aligned in a row.
|
|
91
|
+
/// - Note: The default is `.center`.
|
|
92
|
+
public var verticalAlignment: VerticalAlignment = .center
|
|
93
|
+
|
|
94
|
+
/// The `horizontalAlignment` with its layout direction specifics resolved,
|
|
95
|
+
/// i.e. `.leading` and `.trailing` alignments are mapped to `.left` or `right`,
|
|
96
|
+
/// depending on the current layout direction.
|
|
97
|
+
fileprivate var effectiveHorizontalAlignment: EffectiveHorizontalAlignment {
|
|
98
|
+
|
|
99
|
+
var trivialMapping: [HorizontalAlignment: EffectiveHorizontalAlignment] {
|
|
100
|
+
return [
|
|
101
|
+
.left: .left,
|
|
102
|
+
.right: .right,
|
|
103
|
+
.justified: .justified
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let layoutDirection = UIApplication.shared.userInterfaceLayoutDirection
|
|
108
|
+
|
|
109
|
+
switch layoutDirection {
|
|
110
|
+
case .leftToRight:
|
|
111
|
+
switch horizontalAlignment {
|
|
112
|
+
case .leading:
|
|
113
|
+
return .left
|
|
114
|
+
case .trailing:
|
|
115
|
+
return .right
|
|
116
|
+
default:
|
|
117
|
+
break
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
case .rightToLeft:
|
|
121
|
+
switch horizontalAlignment {
|
|
122
|
+
case .leading:
|
|
123
|
+
return .right
|
|
124
|
+
case .trailing:
|
|
125
|
+
return .left
|
|
126
|
+
default:
|
|
127
|
+
break
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// It's safe to force-unwrap as `.leading` and `.trailing` are covered
|
|
132
|
+
// above and the `trivialMapping` dictionary contains all other keys.
|
|
133
|
+
return trivialMapping[horizontalAlignment]!
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// The vertical axis with respect to which the cells are horizontally aligned.
|
|
137
|
+
/// For a `justified` alignment the alignment axis is not defined and this value is `nil`.
|
|
138
|
+
fileprivate var alignmentAxis: AlignmentAxis<HorizontalAlignment>? {
|
|
139
|
+
switch effectiveHorizontalAlignment {
|
|
140
|
+
case .left:
|
|
141
|
+
return AlignmentAxis(alignment: HorizontalAlignment.left, position: sectionInset.left)
|
|
142
|
+
case .right:
|
|
143
|
+
guard let collectionViewWidth = collectionView?.frame.size.width else {
|
|
144
|
+
return nil
|
|
145
|
+
}
|
|
146
|
+
return AlignmentAxis(alignment: HorizontalAlignment.right, position: collectionViewWidth - sectionInset.right)
|
|
147
|
+
default:
|
|
148
|
+
return nil
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// The width of the area inside the collection view that can be filled with cells.
|
|
153
|
+
private var contentWidth: CGFloat? {
|
|
154
|
+
guard let collectionViewWidth = collectionView?.frame.size.width else {
|
|
155
|
+
return nil
|
|
156
|
+
}
|
|
157
|
+
return collectionViewWidth - sectionInset.left - sectionInset.right
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
// MARK: - 👶 Initialization
|
|
162
|
+
|
|
163
|
+
/// The designated initializer.
|
|
164
|
+
///
|
|
165
|
+
/// - Parameters:
|
|
166
|
+
/// - horizontalAlignment: Specifies how the cells are horizontally aligned in a row. --
|
|
167
|
+
/// (Default: `.justified`)
|
|
168
|
+
/// - verticalAlignment: Specified how the cells are vertically aligned in a row. --
|
|
169
|
+
/// (Default: `.center`)
|
|
170
|
+
public init(horizontalAlignment: HorizontalAlignment = .justified, verticalAlignment: VerticalAlignment = .center) {
|
|
171
|
+
super.init()
|
|
172
|
+
self.horizontalAlignment = horizontalAlignment
|
|
173
|
+
self.verticalAlignment = verticalAlignment
|
|
174
|
+
}
|
|
175
|
+
override public init() {
|
|
176
|
+
super.init()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
required public init?(coder aDecoder: NSCoder) {
|
|
180
|
+
super.init(coder: aDecoder)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
// MARK: - 🅾️ Overrides
|
|
185
|
+
|
|
186
|
+
override open func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
|
187
|
+
|
|
188
|
+
// 💡 IDEA:
|
|
189
|
+
// The approach for computing a cell's frame is to create a rectangle that covers the current line.
|
|
190
|
+
// Then we check if the preceding cell's frame intersects with this rectangle.
|
|
191
|
+
// If it does, the current item is not the first item in the line. Otherwise it is.
|
|
192
|
+
// (Vice-versa for right-aligned cells.)
|
|
193
|
+
//
|
|
194
|
+
// +---------+----------------------------------------------------------------+---------+
|
|
195
|
+
// | | | |
|
|
196
|
+
// | | +------------+ | |
|
|
197
|
+
// | | | | | |
|
|
198
|
+
// | section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
|
|
199
|
+
// | inset | |intersection| | | line rect | inset |
|
|
200
|
+
// | |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| |
|
|
201
|
+
// | (left) | | | current item | (right) |
|
|
202
|
+
// | | +------------+ | |
|
|
203
|
+
// | | previous item | |
|
|
204
|
+
// +---------+----------------------------------------------------------------+---------+
|
|
205
|
+
//
|
|
206
|
+
// ℹ️ We need this rather complicated approach because the first item in a line
|
|
207
|
+
// is not always left-aligned and the last item in a line is not always right-aligned:
|
|
208
|
+
// If there is only one item in a line UICollectionViewFlowLayout will center it.
|
|
209
|
+
|
|
210
|
+
// We may not change the original layout attributes or UICollectionViewFlowLayout might complain.
|
|
211
|
+
guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
|
|
212
|
+
return nil
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// For a justified layout there's nothing to do here
|
|
216
|
+
// as UICollectionViewFlowLayout justifies the items in a line by default.
|
|
217
|
+
if horizontalAlignment != .justified {
|
|
218
|
+
layoutAttributes.alignHorizontally(collectionViewLayout: self)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// For a vertically centered layout there's nothing to do here
|
|
222
|
+
// as UICollectionViewFlowLayout center-aligns the items in a line by default.
|
|
223
|
+
if verticalAlignment != .center {
|
|
224
|
+
layoutAttributes.alignVertically(collectionViewLayout: self)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return layoutAttributes
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
|
231
|
+
// We may not change the original layout attributes or UICollectionViewFlowLayout might complain.
|
|
232
|
+
let layoutAttributesObjects = copy(super.layoutAttributesForElements(in: rect))
|
|
233
|
+
layoutAttributesObjects?.forEach({ (layoutAttributes) in
|
|
234
|
+
setFrame(forLayoutAttributes: layoutAttributes)
|
|
235
|
+
})
|
|
236
|
+
return layoutAttributesObjects
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
// MARK: - 👷 Private layout helpers
|
|
241
|
+
|
|
242
|
+
/// Sets the frame for the passed layout attributes object by calling the `layoutAttributesForItem(at:)` function.
|
|
243
|
+
private func setFrame(forLayoutAttributes layoutAttributes: UICollectionViewLayoutAttributes) {
|
|
244
|
+
if layoutAttributes.representedElementCategory == .cell { // Do not modify header views etc.
|
|
245
|
+
let indexPath = layoutAttributes.indexPath
|
|
246
|
+
if let newFrame = layoutAttributesForItem(at: indexPath)?.frame {
|
|
247
|
+
layoutAttributes.frame = newFrame
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// A function to access the `super` implementation of `layoutAttributesForItem(at:)` externally.
|
|
253
|
+
///
|
|
254
|
+
/// - Parameter indexPath: The index path of the item for which to return the layout attributes.
|
|
255
|
+
/// - Returns: The unmodified layout attributes for the item at the specified index path
|
|
256
|
+
/// as computed by `UICollectionViewFlowLayout`.
|
|
257
|
+
fileprivate func originalLayoutAttribute(forItemAt indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
|
258
|
+
return super.layoutAttributesForItem(at: indexPath)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/// Determines if the `firstItemAttributes`' frame is in the same line
|
|
262
|
+
/// as the `secondItemAttributes`' frame.
|
|
263
|
+
///
|
|
264
|
+
/// - Parameters:
|
|
265
|
+
/// - firstItemAttributes: The first layout attributes object to be compared.
|
|
266
|
+
/// - secondItemAttributes: The second layout attributes object to be compared.
|
|
267
|
+
/// - Returns: `true` if the frames of the two layout attributes are in the same line, else `false`.
|
|
268
|
+
/// `false` is also returned when the layout's `collectionView` property is `nil`.
|
|
269
|
+
fileprivate func isFrame(for firstItemAttributes: UICollectionViewLayoutAttributes, inSameLineAsFrameFor secondItemAttributes: UICollectionViewLayoutAttributes) -> Bool {
|
|
270
|
+
guard let lineWidth = contentWidth else {
|
|
271
|
+
return false
|
|
272
|
+
}
|
|
273
|
+
let firstItemFrame = firstItemAttributes.frame
|
|
274
|
+
let lineFrame = CGRect(x: sectionInset.left,
|
|
275
|
+
y: firstItemFrame.origin.y,
|
|
276
|
+
width: lineWidth,
|
|
277
|
+
height: firstItemFrame.size.height)
|
|
278
|
+
return lineFrame.intersects(secondItemAttributes.frame)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/// Determines the layout attributes objects for all items displayed in the same line as the item
|
|
282
|
+
/// represented by the passed `layoutAttributes` object.
|
|
283
|
+
///
|
|
284
|
+
/// - Parameter layoutAttributes: The layout attributed that represents the reference item.
|
|
285
|
+
/// - Returns: The layout attributes objects representing all other items in the same line.
|
|
286
|
+
/// The passed `layoutAttributes` object itself is always contained in the returned array.
|
|
287
|
+
fileprivate func layoutAttributes(forItemsInLineWith layoutAttributes: UICollectionViewLayoutAttributes) -> [UICollectionViewLayoutAttributes] {
|
|
288
|
+
guard let lineWidth = contentWidth else {
|
|
289
|
+
return [layoutAttributes]
|
|
290
|
+
}
|
|
291
|
+
var lineFrame = layoutAttributes.frame
|
|
292
|
+
lineFrame.origin.x = sectionInset.left
|
|
293
|
+
lineFrame.size.width = lineWidth
|
|
294
|
+
return super.layoutAttributesForElements(in: lineFrame) ?? []
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/// Copmutes the alignment axis with which to align the items represented by the `layoutAttributes` objects vertically.
|
|
298
|
+
///
|
|
299
|
+
/// - Parameter layoutAttributes: The layout attributes objects to be vertically aligned.
|
|
300
|
+
/// - Returns: The axis with respect to which the layout attributes can be aligned
|
|
301
|
+
/// or `nil` if the `layoutAttributes` array is empty.
|
|
302
|
+
private func verticalAlignmentAxisForLine(with layoutAttributes: [UICollectionViewLayoutAttributes]) -> AlignmentAxis<VerticalAlignment>? {
|
|
303
|
+
|
|
304
|
+
guard let firstAttribute = layoutAttributes.first else {
|
|
305
|
+
return nil
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
switch verticalAlignment {
|
|
309
|
+
case .top:
|
|
310
|
+
let minY = layoutAttributes.reduce(CGFloat.greatestFiniteMagnitude) { min($0, $1.frame.minY) }
|
|
311
|
+
return AlignmentAxis(alignment: .top, position: minY)
|
|
312
|
+
|
|
313
|
+
case .bottom:
|
|
314
|
+
let maxY = layoutAttributes.reduce(0) { max($0, $1.frame.maxY) }
|
|
315
|
+
return AlignmentAxis(alignment: .bottom, position: maxY)
|
|
316
|
+
|
|
317
|
+
default:
|
|
318
|
+
let centerY = firstAttribute.center.y
|
|
319
|
+
return AlignmentAxis(alignment: .center, position: centerY)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/// Computes the axis with which to align the item represented by the `currentLayoutAttributes` vertically.
|
|
324
|
+
///
|
|
325
|
+
/// - Parameter currentLayoutAttributes: The layout attributes representing the item to be vertically aligned.
|
|
326
|
+
/// - Returns: The axis with respect to which the item can be aligned.
|
|
327
|
+
fileprivate func verticalAlignmentAxis(for currentLayoutAttributes: UICollectionViewLayoutAttributes) -> AlignmentAxis<VerticalAlignment> {
|
|
328
|
+
let layoutAttributesInLine = layoutAttributes(forItemsInLineWith: currentLayoutAttributes)
|
|
329
|
+
// It's okay to force-unwrap here because we pass a non-empty array.
|
|
330
|
+
return verticalAlignmentAxisForLine(with: layoutAttributesInLine)!
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/// Creates a deep copy of the passed array by copying all its items.
|
|
334
|
+
///
|
|
335
|
+
/// - Parameter layoutAttributesArray: The array to be copied.
|
|
336
|
+
/// - Returns: A deep copy of the passed array.
|
|
337
|
+
private func copy(_ layoutAttributesArray: [UICollectionViewLayoutAttributes]?) -> [UICollectionViewLayoutAttributes]? {
|
|
338
|
+
return layoutAttributesArray?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
// MARK: - 👷 Layout attributes helpers
|
|
346
|
+
|
|
347
|
+
fileprivate extension UICollectionViewLayoutAttributes {
|
|
348
|
+
|
|
349
|
+
private var currentSection: Int {
|
|
350
|
+
return indexPath.section
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private var currentItem: Int {
|
|
354
|
+
return indexPath.item
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/// The index path for the item preceding the item represented by this layout attributes object.
|
|
358
|
+
private var precedingIndexPath: IndexPath {
|
|
359
|
+
return IndexPath(item: currentItem - 1, section: currentSection)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/// The index path for the item following the item represented by this layout attributes object.
|
|
363
|
+
private var followingIndexPath: IndexPath {
|
|
364
|
+
return IndexPath(item: currentItem + 1, section: currentSection)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/// Checks if the item represetend by this layout attributes object is the first item in the line.
|
|
368
|
+
///
|
|
369
|
+
/// - Parameter collectionViewLayout: The layout for which to perform the check.
|
|
370
|
+
/// - Returns: `true` if the represented item is the first item in the line, else `false`.
|
|
371
|
+
func isRepresentingFirstItemInLine(collectionViewLayout: AlignedCollectionViewFlowLayout) -> Bool {
|
|
372
|
+
if currentItem <= 0 {
|
|
373
|
+
return true
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
if let layoutAttributesForPrecedingItem = collectionViewLayout.originalLayoutAttribute(forItemAt: precedingIndexPath) {
|
|
377
|
+
return !collectionViewLayout.isFrame(for: self, inSameLineAsFrameFor: layoutAttributesForPrecedingItem)
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
return true
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/// Checks if the item represetend by this layout attributes object is the last item in the line.
|
|
386
|
+
///
|
|
387
|
+
/// - Parameter collectionViewLayout: The layout for which to perform the check.
|
|
388
|
+
/// - Returns: `true` if the represented item is the last item in the line, else `false`.
|
|
389
|
+
func isRepresentingLastItemInLine(collectionViewLayout: AlignedCollectionViewFlowLayout) -> Bool {
|
|
390
|
+
guard let itemCount = collectionViewLayout.collectionView?.numberOfItems(inSection: currentSection) else {
|
|
391
|
+
return false
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if currentItem >= itemCount - 1 {
|
|
395
|
+
return true
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
if let layoutAttributesForFollowingItem = collectionViewLayout.originalLayoutAttribute(forItemAt: followingIndexPath) {
|
|
399
|
+
return !collectionViewLayout.isFrame(for: self, inSameLineAsFrameFor: layoutAttributesForFollowingItem)
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
return true
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/// Moves the layout attributes object's frame so that it is aligned horizontally with the alignment axis.
|
|
408
|
+
func align(toAlignmentAxis alignmentAxis: AlignmentAxis<HorizontalAlignment>) {
|
|
409
|
+
switch alignmentAxis.alignment {
|
|
410
|
+
case .left:
|
|
411
|
+
frame.origin.x = alignmentAxis.position
|
|
412
|
+
case .right:
|
|
413
|
+
frame.origin.x = alignmentAxis.position - frame.size.width
|
|
414
|
+
default:
|
|
415
|
+
break
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/// Moves the layout attributes object's frame so that it is aligned vertically with the alignment axis.
|
|
420
|
+
func align(toAlignmentAxis alignmentAxis: AlignmentAxis<VerticalAlignment>) {
|
|
421
|
+
switch alignmentAxis.alignment {
|
|
422
|
+
case .top:
|
|
423
|
+
frame.origin.y = alignmentAxis.position
|
|
424
|
+
case .bottom:
|
|
425
|
+
frame.origin.y = alignmentAxis.position - frame.size.height
|
|
426
|
+
default:
|
|
427
|
+
center.y = alignmentAxis.position
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/// Positions the frame right of the preceding item's frame, leaving a spacing between the frames
|
|
432
|
+
/// as defined by the collection view layout's `minimumInteritemSpacing`.
|
|
433
|
+
///
|
|
434
|
+
/// - Parameter collectionViewLayout: The layout on which to perfom the calculations.
|
|
435
|
+
private func alignToPrecedingItem(collectionViewLayout: AlignedCollectionViewFlowLayout) {
|
|
436
|
+
let itemSpacing = collectionViewLayout.minimumInteritemSpacing
|
|
437
|
+
|
|
438
|
+
if let precedingItemAttributes = collectionViewLayout.layoutAttributesForItem(at: precedingIndexPath) {
|
|
439
|
+
frame.origin.x = precedingItemAttributes.frame.maxX + itemSpacing
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/// Positions the frame left of the following item's frame, leaving a spacing between the frames
|
|
444
|
+
/// as defined by the collection view layout's `minimumInteritemSpacing`.
|
|
445
|
+
///
|
|
446
|
+
/// - Parameter collectionViewLayout: The layout on which to perfom the calculations.
|
|
447
|
+
private func alignToFollowingItem(collectionViewLayout: AlignedCollectionViewFlowLayout) {
|
|
448
|
+
let itemSpacing = collectionViewLayout.minimumInteritemSpacing
|
|
449
|
+
|
|
450
|
+
if let followingItemAttributes = collectionViewLayout.layoutAttributesForItem(at: followingIndexPath) {
|
|
451
|
+
frame.origin.x = followingItemAttributes.frame.minX - itemSpacing - frame.size.width
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/// Aligns the frame horizontally as specified by the collection view layout's `horizontalAlignment`.
|
|
456
|
+
///
|
|
457
|
+
/// - Parameters:
|
|
458
|
+
/// - collectionViewLayout: The layout providing the alignment information.
|
|
459
|
+
func alignHorizontally(collectionViewLayout: AlignedCollectionViewFlowLayout) {
|
|
460
|
+
|
|
461
|
+
guard let alignmentAxis = collectionViewLayout.alignmentAxis else {
|
|
462
|
+
return
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
switch collectionViewLayout.effectiveHorizontalAlignment {
|
|
466
|
+
|
|
467
|
+
case .left:
|
|
468
|
+
if isRepresentingFirstItemInLine(collectionViewLayout: collectionViewLayout) {
|
|
469
|
+
align(toAlignmentAxis: alignmentAxis)
|
|
470
|
+
} else {
|
|
471
|
+
alignToPrecedingItem(collectionViewLayout: collectionViewLayout)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
case .right:
|
|
475
|
+
if isRepresentingLastItemInLine(collectionViewLayout: collectionViewLayout) {
|
|
476
|
+
align(toAlignmentAxis: alignmentAxis)
|
|
477
|
+
} else {
|
|
478
|
+
alignToFollowingItem(collectionViewLayout: collectionViewLayout)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
default:
|
|
482
|
+
return
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/// Aligns the frame vertically as specified by the collection view layout's `verticalAlignment`.
|
|
487
|
+
///
|
|
488
|
+
/// - Parameter collectionViewLayout: The layout providing the alignment information.
|
|
489
|
+
func alignVertically(collectionViewLayout: AlignedCollectionViewFlowLayout) {
|
|
490
|
+
let alignmentAxis = collectionViewLayout.verticalAlignmentAxis(for: self)
|
|
491
|
+
align(toAlignmentAxis: alignmentAxis)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
declare class AlignedCollectionViewFlowLayout extends UICollectionViewFlowLayout {
|
|
3
|
+
|
|
4
|
+
static alloc(): AlignedCollectionViewFlowLayout; // inherited from NSObject
|
|
5
|
+
|
|
6
|
+
static new(): AlignedCollectionViewFlowLayout; // inherited from NSObject
|
|
7
|
+
|
|
8
|
+
horizontalAlignment: HorizontalAlignment;
|
|
9
|
+
|
|
10
|
+
verticalAlignment: VerticalAlignment;
|
|
11
|
+
|
|
12
|
+
constructor(o: { horizontalAlignment: HorizontalAlignment; verticalAlignment: VerticalAlignment; });
|
|
13
|
+
|
|
14
|
+
initWithHorizontalAlignmentVerticalAlignment(horizontalAlignment: HorizontalAlignment, verticalAlignment: VerticalAlignment): this;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare const enum HorizontalAlignment {
|
|
18
|
+
|
|
19
|
+
Left = 0,
|
|
20
|
+
|
|
21
|
+
Right = 1,
|
|
22
|
+
|
|
23
|
+
Leading = 2,
|
|
24
|
+
|
|
25
|
+
Trailing = 3,
|
|
26
|
+
|
|
27
|
+
Justified = 4
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare const enum VerticalAlignment {
|
|
31
|
+
|
|
32
|
+
Top = 0,
|
|
33
|
+
|
|
34
|
+
Center = 1,
|
|
35
|
+
|
|
36
|
+
Bottom = 2
|
|
37
|
+
}
|