Skip to content

Commit dcea58d

Browse files
nuomi1jessesquires
andauthored
Implement willDisplay and didEndDisplaying APIs (#121)
Closes #96 Implements `willDisplay` and `didEndDisplaying`. --------- Co-authored-by: Jesse Squires <jesse@jessesquires.com>
1 parent 021779e commit dcea58d

13 files changed

+352
-16
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ NEXT
77

88
- TBA
99

10+
0.1.4
11+
-----
12+
13+
- Implemented `willDisplay()` and `didEndDisplaying()` APIs for both `CellViewModel` and `SupplementaryViewModel`. ([@nuomi1](https://github.com/nuomi1), [#121](https://github.com/jessesquires/ReactiveCollectionsKit/pull/121))
14+
1015
0.1.3
1116
-----
1217

Sources/CellViewModel.swift

+30-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ public protocol CellViewModel: DiffableViewModel, ViewRegistrationProvider {
3636
/// Handles the selection event for this cell, optionally using the provided `coordinator`.
3737
/// - Parameter coordinator: An event coordinator object, if one was provided to the `CollectionViewDriver`.
3838
func didSelect(with coordinator: CellEventCoordinator?)
39+
40+
/// Tells the view model that its cell is about to be displayed in the collection view.
41+
/// This corresponds to the delegate method `collectionView(_:willDisplay:forItemAt:)`.
42+
func willDisplay()
43+
44+
/// Tells the view model that its cell was removed from the collection view.
45+
/// This corresponds to the delegate method `collectionView(_:didEndDisplaying:forItemAt:)`.
46+
func didEndDisplaying()
3947
}
4048

4149
extension CellViewModel {
@@ -51,6 +59,12 @@ extension CellViewModel {
5159
public func didSelect(with coordinator: CellEventCoordinator?) {
5260
coordinator?.didSelectCell(viewModel: self)
5361
}
62+
63+
/// Default implementation. Does nothing.
64+
public func willDisplay() { }
65+
66+
/// Default implementation. Does nothing.
67+
public func didEndDisplaying() { }
5468
}
5569

5670
extension CellViewModel {
@@ -124,10 +138,20 @@ public struct AnyCellViewModel: CellViewModel {
124138
self._didSelect(coordinator)
125139
}
126140

127-
/// :nodoc: "override" extension
141+
/// :nodoc:
142+
public func willDisplay() {
143+
self._willDisplay()
144+
}
145+
146+
/// :nodoc:
147+
public func didEndDisplaying() {
148+
self._didEndDisplaying()
149+
}
150+
151+
/// :nodoc: "override" the extension
128152
public let cellClass: AnyClass
129153

130-
/// :nodoc: "override" extension
154+
/// :nodoc: "override" the extension
131155
public let reuseIdentifier: String
132156

133157
// MARK: Private
@@ -139,6 +163,8 @@ public struct AnyCellViewModel: CellViewModel {
139163
private let _contextMenuConfiguration: UIContextMenuConfiguration?
140164
private let _configure: (CellType) -> Void
141165
private let _didSelect: (CellEventCoordinator?) -> Void
166+
private let _willDisplay: () -> Void
167+
private let _didEndDisplaying: () -> Void
142168

143169
// MARK: Init
144170

@@ -162,6 +188,8 @@ public struct AnyCellViewModel: CellViewModel {
162188
self._didSelect = { coordinator in
163189
viewModel.didSelect(with: coordinator)
164190
}
191+
self._willDisplay = viewModel.willDisplay
192+
self._didEndDisplaying = viewModel.didEndDisplaying
165193
self.cellClass = viewModel.cellClass
166194
self.reuseIdentifier = viewModel.reuseIdentifier
167195
}

Sources/CollectionViewDriver.swift

+38
Original file line numberDiff line numberDiff line change
@@ -286,22 +286,60 @@ public final class CollectionViewDriver: NSObject {
286286
// MARK: UICollectionViewDelegate
287287

288288
extension CollectionViewDriver: UICollectionViewDelegate {
289+
// MARK: Managing the selected cells
290+
289291
/// :nodoc:
290292
public func collectionView(_ collectionView: UICollectionView,
291293
didSelectItemAt indexPath: IndexPath) {
292294
self.viewModel.cellViewModel(at: indexPath).didSelect(with: self._cellEventCoordinator)
293295
}
294296

297+
// MARK: Managing cell highlighting
298+
295299
/// :nodoc:
296300
public func collectionView(_ collectionView: UICollectionView,
297301
shouldHighlightItemAt indexPath: IndexPath) -> Bool {
298302
self.viewModel.cellViewModel(at: indexPath).shouldHighlight
299303
}
300304

305+
// MARK: Managing context menus
306+
301307
/// :nodoc:
302308
public func collectionView(_ collectionView: UICollectionView,
303309
contextMenuConfigurationForItemAt indexPath: IndexPath,
304310
point: CGPoint) -> UIContextMenuConfiguration? {
305311
self.viewModel.cellViewModel(at: indexPath).contextMenuConfiguration
306312
}
313+
314+
// MARK: Tracking the addition and removal of views
315+
316+
/// :nodoc:
317+
public func collectionView(_ collectionView: UICollectionView,
318+
willDisplay cell: UICollectionViewCell,
319+
forItemAt indexPath: IndexPath) {
320+
self.viewModel._safeCellViewModel(at: indexPath)?.willDisplay()
321+
}
322+
323+
/// :nodoc:
324+
public func collectionView(_ collectionView: UICollectionView,
325+
willDisplaySupplementaryView view: UICollectionReusableView,
326+
forElementKind elementKind: String,
327+
at indexPath: IndexPath) {
328+
self.viewModel._safeSupplementaryViewModel(ofKind: elementKind, at: indexPath)?.willDisplay()
329+
}
330+
331+
/// :nodoc:
332+
public func collectionView(_ collectionView: UICollectionView,
333+
didEndDisplaying cell: UICollectionViewCell,
334+
forItemAt indexPath: IndexPath) {
335+
self.viewModel._safeCellViewModel(at: indexPath)?.didEndDisplaying()
336+
}
337+
338+
/// :nodoc:
339+
public func collectionView(_ collectionView: UICollectionView,
340+
didEndDisplayingSupplementaryView view: UICollectionReusableView,
341+
forElementOfKind elementKind: String,
342+
at indexPath: IndexPath) {
343+
self.viewModel._safeSupplementaryViewModel(ofKind: elementKind, at: indexPath)?.didEndDisplaying()
344+
}
307345
}

Sources/CollectionViewModel.swift

+36
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,42 @@ public struct CollectionViewModel: Hashable, DiffableViewModel {
152152

153153
// MARK: Internal
154154

155+
func _safeSectionViewModel(at index: Int) -> SectionViewModel? {
156+
guard index < self.count else {
157+
return nil
158+
}
159+
return self.sectionViewModel(at: index)
160+
}
161+
162+
func _safeCellViewModel(at indexPath: IndexPath) -> AnyCellViewModel? {
163+
guard let section = self._safeSectionViewModel(at: indexPath.section),
164+
indexPath.item < section.cells.count else {
165+
return nil
166+
}
167+
return self.cellViewModel(at: indexPath)
168+
}
169+
170+
func _safeSupplementaryViewModel(ofKind kind: String, at indexPath: IndexPath) -> AnySupplementaryViewModel? {
171+
guard let section = self._safeSectionViewModel(at: indexPath.section) else {
172+
return nil
173+
}
174+
175+
if kind == section.header?._kind {
176+
return section.header
177+
}
178+
179+
if kind == section.footer?._kind {
180+
return section.footer
181+
}
182+
183+
let supplementaryViews = section.supplementaryViews.filter { $0._kind == kind }
184+
guard indexPath.item < supplementaryViews.count else {
185+
return nil
186+
}
187+
188+
return self.supplementaryViewModel(ofKind: kind, at: indexPath)
189+
}
190+
155191
func allRegistrations() -> Set<ViewRegistration> {
156192
var all = Set<ViewRegistration>()
157193
self.sections.forEach {

Sources/SupplementaryViewModel.swift

+32-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,22 @@ public protocol SupplementaryViewModel: DiffableViewModel, ViewRegistrationProvi
2626
/// Configures the provided view for display in the collection.
2727
/// - Parameter view: The view to configure.
2828
func configure(view: ViewType)
29+
30+
/// Tells the view model that its supplementary view is about to be displayed in the collection view.
31+
/// This corresponds to the delegate method `collectionView(_:willDisplaySupplementaryView:forElementKind:at:)`.
32+
func willDisplay()
33+
34+
/// Tells the view model that its supplementary view was removed from the collection view.
35+
/// This corresponds to the delegate method `collectionView(_:didEndDisplayingSupplementaryView:forElementOfKind:at:)`.
36+
func didEndDisplaying()
37+
}
38+
39+
extension SupplementaryViewModel {
40+
/// Default implementation. Does nothing.
41+
public func willDisplay() { }
42+
43+
/// Default implementation. Does nothing.
44+
public func didEndDisplaying() { }
2945
}
3046

3147
extension SupplementaryViewModel {
@@ -91,10 +107,20 @@ public struct AnySupplementaryViewModel: SupplementaryViewModel {
91107
self._configure(view)
92108
}
93109

94-
/// :nodoc: "override" extension
110+
/// :nodoc:
111+
public func willDisplay() {
112+
self._willDisplay()
113+
}
114+
115+
/// :nodoc:
116+
public func didEndDisplaying() {
117+
self._didEndDisplaying()
118+
}
119+
120+
/// :nodoc: "override" the extension
95121
public let viewClass: AnyClass
96122

97-
/// :nodoc: "override" extension
123+
/// :nodoc: "override" the extension
98124
public let reuseIdentifier: String
99125

100126
// MARK: Private
@@ -103,6 +129,8 @@ public struct AnySupplementaryViewModel: SupplementaryViewModel {
103129
private let _id: UniqueIdentifier
104130
private let _registration: ViewRegistration
105131
private let _configure: (ViewType) -> Void
132+
private let _willDisplay: () -> Void
133+
private let _didEndDisplaying: () -> Void
106134

107135
// MARK: Init
108136

@@ -121,6 +149,8 @@ public struct AnySupplementaryViewModel: SupplementaryViewModel {
121149
precondition(view is T.ViewType, "View must be of type \(T.ViewType.self). Found \(view.self)")
122150
viewModel.configure(view: view as! T.ViewType)
123151
}
152+
self._willDisplay = viewModel.willDisplay
153+
self._didEndDisplaying = viewModel.didEndDisplaying
124154
self.viewClass = viewModel.viewClass
125155
self.reuseIdentifier = viewModel.reuseIdentifier
126156
}

Tests/Fakes/FakeCellNibView.swift

+10
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ struct FakeCellNibViewModel: CellViewModel {
4444
self.expectationDidSelect?.fulfillAndLog()
4545
}
4646

47+
var expectationWillDisplay: XCTestExpectation?
48+
func willDisplay() {
49+
self.expectationWillDisplay?.fulfillAndLog()
50+
}
51+
52+
var expectationDidEndDisplaying: XCTestExpectation?
53+
func didEndDisplaying() {
54+
self.expectationDidEndDisplaying?.fulfillAndLog()
55+
}
56+
4757
nonisolated static func == (left: Self, right: Self) -> Bool {
4858
left.id == right.id
4959
}

0 commit comments

Comments
 (0)