-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathStickyHeaderFlowLayout.swift
139 lines (95 loc) · 5.72 KB
/
StickyHeaderFlowLayout.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//
// Created by Pete Callaway on 10/01/2015.
// Copyright (c) 2015 Dative Studios. All rights reserved.
//
import UIKit
class StickyHeaderFlowLayout: UICollectionViewFlowLayout {
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
// Return true so we're asked for layout attributes as the content is scrolled
return true
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
// Get the layout attributes for a standard UICollectionViewFlowLayout
var elementsLayoutAttributes = super.layoutAttributesForElementsInRect(rect) as? [UICollectionViewLayoutAttributes]
if elementsLayoutAttributes == nil {
return nil
}
// Define a struct we can use to store optional layout attributes in a dictionary
struct HeaderAttributes {
var layoutAttributes: UICollectionViewLayoutAttributes?
}
var visibleSectionHeaderLayoutAttributes = [Int : HeaderAttributes]()
// Loop through the layout attributes we have
for (index, layoutAttributes) in enumerate(elementsLayoutAttributes!) {
let section = layoutAttributes.indexPath.section
switch layoutAttributes.representedElementCategory {
case .SupplementaryView:
// If this is a set of layout attributes for a section header, replace them with modified attributes
if layoutAttributes.representedElementKind == UICollectionElementKindSectionHeader {
let newLayoutAttributes = layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: layoutAttributes.indexPath)
elementsLayoutAttributes![index] = newLayoutAttributes
// Store the layout attributes in the dictionary so we know they've been dealt with
visibleSectionHeaderLayoutAttributes[section] = HeaderAttributes(layoutAttributes: newLayoutAttributes)
}
case .Cell:
// Check if this is a cell for a section we've not dealt with yet
if visibleSectionHeaderLayoutAttributes[section] == nil {
// Stored a struct for this cell's section so we can can fill it out later if needed
visibleSectionHeaderLayoutAttributes[section] = HeaderAttributes(layoutAttributes: nil)
}
case .DecorationView:
break
}
}
// Loop through the sections we've found
for (section, headerAttributes) in visibleSectionHeaderLayoutAttributes {
// If the header for this section hasn't been set up, do it now
if headerAttributes.layoutAttributes == nil {
let newAttributes = layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: NSIndexPath(forItem: 0, inSection: section))
elementsLayoutAttributes!.append(newAttributes)
}
}
return elementsLayoutAttributes
}
override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
// Get the layout attributes for a standard flow layout
let attributes = super.layoutAttributesForSupplementaryViewOfKind(elementKind, atIndexPath: indexPath)
// If this is a header, we should tweak it's attributes
if elementKind == UICollectionElementKindSectionHeader {
if let fullSectionFrame = frameForSection(indexPath.section) {
let minimumY = max(collectionView!.contentOffset.y + collectionView!.contentInset.top, fullSectionFrame.origin.y)
let maximumY = CGRectGetMaxY(fullSectionFrame) - headerReferenceSize.height - collectionView!.contentInset.bottom
attributes.frame = CGRect(x: 0, y: min(minimumY, maximumY), width: collectionView!.bounds.size.width, height: headerReferenceSize.height)
attributes.zIndex = 1
}
}
return attributes
}
// MARK: Private helper methods
private func frameForSection(section: Int) -> CGRect? {
// Sanity check
let numberOfItems = collectionView!.numberOfItemsInSection(section)
if numberOfItems == 0 {
return nil
}
// Get the index paths for the first and last cell in the section
let firstIndexPath = NSIndexPath(forRow: 0, inSection: section)
let lastIndexPath = numberOfItems == 0 ? firstIndexPath : NSIndexPath(forRow: numberOfItems - 1, inSection: section)
// Work out the top of the first cell and bottom of the last cell
var firstCellTop = layoutAttributesForItemAtIndexPath(firstIndexPath).frame.origin.y
let lastCellBottom = CGRectGetMaxY(layoutAttributesForItemAtIndexPath(lastIndexPath).frame)
// Build the frame for the section
var frame = CGRectZero
frame.size.width = collectionView!.bounds.size.width
frame.origin.y = firstCellTop
frame.size.height = lastCellBottom - firstCellTop
// Increase the frame to allow space for the header
frame.origin.y -= headerReferenceSize.height
frame.size.height += headerReferenceSize.height
// Increase the frame to allow space for an section insets
frame.origin.y -= sectionInset.top
frame.size.height += sectionInset.top
frame.size.height += sectionInset.bottom
return frame
}
}