Skip to content

Commit 4d703b0

Browse files
committed
FHDiffableCollectionViewController
1 parent 155c2d4 commit 4d703b0

File tree

1 file changed

+206
-0
lines changed

1 file changed

+206
-0
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#if !os(macOS)
2+
3+
import UIKit
4+
5+
/// A subclass of **UICollectionViewController** with diffable data source.
6+
open class FHDiffableCollectionViewController<SectionIdentifierType, ItemIdentifierType>: UICollectionViewController where SectionIdentifierType: Hashable, ItemIdentifierType: Hashable {
7+
8+
// MARK: - Enums
9+
10+
/// The type of layout you want to initialize your collection view with.
11+
public enum LayoutType {
12+
13+
/// Creates a default **UICollectionViewCompositionalLayout**. This is intended to be used for test purposes.
14+
case `default`
15+
16+
/// Use a **UICollectionViewFlowLayout**.
17+
case flow(UICollectionViewFlowLayout)
18+
19+
/// Use a **UICollectionViewCompositionalLayout**.
20+
case compositional(UICollectionViewCompositionalLayout)
21+
22+
/// Use your custom **UICollectionViewLayout**.
23+
case custom(UICollectionViewLayout)
24+
25+
}
26+
27+
28+
// MARK: - Typealias
29+
30+
/// A typealias for **UICollectionViewDiffableDataSource** type.
31+
public typealias FHDataSource = UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>
32+
33+
/// A typealias for **NSDiffableDataSourceSnapshot** type.
34+
public typealias FHSnapshot = NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>
35+
36+
/// A typealias for **FHDiffableDataSourceSnapshotSection** array type.
37+
public typealias FHSnapshotData = [FHDiffableDataSourceSnapshotSection<SectionIdentifierType, ItemIdentifierType>]
38+
39+
40+
// MARK: - Initializers
41+
42+
/// Initializes a **FHDiffableCollectionViewController** and configures the collection view with the provided layout.
43+
///
44+
/// - Parameters:
45+
/// - layoutType: The type of layout the collection view should use.
46+
///
47+
/// - Returns: An initialized **FHDiffableCollectionViewController** object.
48+
public init(layout layoutType: LayoutType = .default) {
49+
switch layoutType {
50+
case .default:
51+
let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .fractionalWidth(1/3), heightDimension: .fractionalWidth(1/3)))
52+
item.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)
53+
let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .estimated(0)), subitems: [item])
54+
group.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)
55+
let section = NSCollectionLayoutSection(group: group)
56+
let headerItem = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .estimated(32)), elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
57+
headerItem.pinToVisibleBounds = true
58+
section.boundarySupplementaryItems = [headerItem]
59+
let compositionalLayout = UICollectionViewCompositionalLayout(section: section)
60+
super.init(collectionViewLayout: compositionalLayout)
61+
case .flow(let flowLayout):
62+
super.init(collectionViewLayout: flowLayout)
63+
case .compositional(let compositionalLayout):
64+
super.init(collectionViewLayout: compositionalLayout)
65+
case .custom(let layout):
66+
super.init(collectionViewLayout: layout)
67+
}
68+
}
69+
70+
required public init?(coder: NSCoder) {
71+
super.init(coder: coder)
72+
}
73+
74+
75+
// MARK: - Private Properties
76+
77+
private var _cellProvider: FHDataSource.CellProvider = { (collectionView, indexPath, itemIdentifier) in
78+
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "defaultCell", for: indexPath)
79+
cell.backgroundColor = .systemTeal
80+
cell.layer.cornerRadius = 8
81+
return cell
82+
}
83+
84+
private var _supplementaryViewProvider: FHDataSource.SupplementaryViewProvider = { (collectionView, kind, indexPath) in
85+
let reuseableHeaderView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "defaultHeader", for: indexPath)
86+
reuseableHeaderView.backgroundColor = .systemIndigo
87+
return reuseableHeaderView
88+
}
89+
90+
private lazy var _dataSource: FHDataSource = {
91+
let dataSource = FHDataSource(collectionView: collectionView, cellProvider: cellProvider)
92+
dataSource.supplementaryViewProvider = supplementaryViewProvider
93+
return dataSource
94+
}()
95+
96+
97+
// MARK: - Public Properties
98+
99+
/// The cell provider which creates the cells.
100+
///
101+
/// Override this property to configure a custom cell.
102+
///
103+
/// The default implementation just shows an empty cell.
104+
///
105+
/// override var cellProvider: UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>.CellProvider {
106+
/// return { (collectionView, indexPath, itemIdentifier) in
107+
/// let cell = collectionView.dequeueReusableCell(withIdentifier: /*your identifier*/, for: indexPath) as? CustomCell
108+
/// /*customize your cell here*/
109+
/// return cell
110+
/// }
111+
/// }
112+
///
113+
/// - important: Do not forget to register the reuseable cell before the first snapshot is applied!
114+
///
115+
/// collectionView.register(CustomCell.self, forCellReuseIdentifier: /*your identifier*/) // e.g. in viewDidLoad()
116+
open var cellProvider: UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>.CellProvider {
117+
return _cellProvider
118+
}
119+
120+
/// The supplementary view provider which creates the supplementary views.
121+
///
122+
/// Override this property to configure a custom supplementary view.
123+
///
124+
/// The default implementation just shows an empty section header.
125+
///
126+
/// override var supplementaryViewProvider: UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>.SupplementaryViewProvider {
127+
/// return { (collectionView, kind, indexPath) in
128+
/// let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: /*your identifier*/, for: indexPath) as? CustomReuseableView
129+
/// /*customize your supplementary view here*/
130+
/// return supplementaryView
131+
/// }
132+
/// }
133+
///
134+
/// - important: Do not forget to register the reuseable view before the first snapshot is applied!
135+
///
136+
/// collectionView.register(CustomReuseableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: /*your identifier*/) // e.g. in viewDidLoad()
137+
open var supplementaryViewProvider: UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>.SupplementaryViewProvider {
138+
return _supplementaryViewProvider
139+
}
140+
141+
/// The data source for the collection view.
142+
///
143+
/// Override this property only if a custom **UICollectionViewDiffableDataSource** should be applied.
144+
/// For cell configuration overried the `cellProvider` property.
145+
/// For supplementary view configuration override the `supplementaryViewProvider` property.
146+
///
147+
/// lazy var customDataSource: CustomDataSource = {
148+
/// let dataSource = CustomDataSource(collectionView: collectionView, cellProvider: cellProvider)
149+
/// dataSource.supplementaryViewProvider = supplementaryViewProvider
150+
/// return dataSource
151+
/// }()
152+
///
153+
/// override var dataSource: UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> {
154+
/// return customDataSource
155+
/// }
156+
///
157+
/// - important: You need to use a lazy var for it to work properly!
158+
open var dataSource: UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> {
159+
return _dataSource
160+
}
161+
162+
163+
// MARK: - Public Methods
164+
165+
/// This method applys a new snapshot to the collection view.
166+
///
167+
/// This is the equivalent for `reloadData()` or `performBatchUpdates(_:)`
168+
///
169+
/// With the `animatingDifferences`parameter the update animation can be disabled.
170+
///
171+
/// If you do not want to use **FHSnapshotData** you can write your own `applySnapshot()` method like that:
172+
///
173+
/// func applySnapshot() {
174+
/// var snapshot = FHSnapshot()
175+
/// snapshot.appendSections([/*your sections*/])
176+
/// snapshot.appendItems([/*your items*/], toSection: /*your section*/) // do that for every section
177+
/// dataSource.apply(snapshot, animatingDifferences: true, completion: nil)
178+
/// }
179+
///
180+
/// - Parameters:
181+
/// - snapshotData: The data snapshot for the collection view.
182+
/// - animatingDifferences: A boolean value to deactive the animation. Default value is `true`.
183+
/// - completion: The completion block for when the update is finished. Default value is `nil`.
184+
open func applySnapshot(_ snapshotData: FHSnapshotData, animatingDifferences: Bool = true, completion: (() -> Void)? = nil) {
185+
var snapshot = FHSnapshot()
186+
187+
snapshot.appendSections(snapshotData.map({ $0.sectionIdentifier }))
188+
snapshotData.forEach { (section) in
189+
snapshot.appendItems(section.itemIdentifiers, toSection: section.sectionIdentifier)
190+
}
191+
192+
dataSource.apply(snapshot, animatingDifferences: animatingDifferences, completion: completion)
193+
}
194+
195+
196+
// MARK: - Overrides
197+
198+
open override func viewDidLoad() {
199+
super.viewDidLoad()
200+
201+
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "defaultCell")
202+
collectionView.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "defaultHeader")
203+
}
204+
}
205+
206+
#endif

0 commit comments

Comments
 (0)