Skip to content

Commit 238a869

Browse files
committed
For demo purposes: Remaining pieces + example app
1 parent eb1868b commit 238a869

File tree

5 files changed

+76
-23
lines changed

5 files changed

+76
-23
lines changed

Example/Sources/List/ListViewController.swift

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ final class ListViewController: ExampleViewController, CellEventCoordinator {
8181

8282
private func makeViewModel() -> CollectionViewModel {
8383
// Create People Section
84-
let peopleCellViewModels = self.model.people.map {
84+
let peopleCellViewModels = self.model.people.enumerated().map { index, person in
8585
let menuConfig = UIContextMenuConfiguration.configFor(
86-
itemId: $0.id,
86+
itemId: person.id,
8787
favoriteAction: { [unowned self] in
8888
self.toggleFavorite(id: $0)
8989
},
@@ -92,9 +92,12 @@ final class ListViewController: ExampleViewController, CellEventCoordinator {
9292
}
9393
)
9494

95+
let children = makeViewModel(for: person.subPeople)
96+
9597
return PersonCellViewModelList(
96-
person: $0,
97-
contextMenuConfiguration: menuConfig
98+
person: person,
99+
contextMenuConfiguration: menuConfig,
100+
children: children
98101
).eraseToAnyViewModel()
99102
}
100103
let peopleHeader = HeaderViewModel(title: "People", style: .small)
@@ -134,6 +137,16 @@ final class ListViewController: ExampleViewController, CellEventCoordinator {
134137
// Create final view model
135138
return CollectionViewModel(id: "list_view", sections: [peopleSection, colorSection])
136139
}
140+
141+
private func makeViewModel(for people: [PersonModel]) -> [AnyCellViewModel] {
142+
let children: [AnyCellViewModel] = people.map {
143+
PersonCellViewModelList(person: $0,
144+
contextMenuConfiguration: nil,
145+
children: makeViewModel(for: $0.subPeople)).eraseToAnyViewModel()
146+
}
147+
148+
return children
149+
}
137150
}
138151

139152
extension ListViewController: UIScrollViewDelegate {

Example/Sources/List/PersonCellViewModelList.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ struct PersonCellViewModelList: CellViewModel {
2323

2424
let contextMenuConfiguration: UIContextMenuConfiguration?
2525

26+
let children: [AnyCellViewModel]
27+
2628
func configure(cell: UICollectionViewListCell) {
2729
var contentConfiguration = UIListContentConfiguration.subtitleCell()
2830
contentConfiguration.text = self.person.name
@@ -35,7 +37,7 @@ struct PersonCellViewModelList: CellViewModel {
3537
let flagEmoji = UICellAccessory.customView(
3638
configuration: .init(customView: label, placement: .leading())
3739
)
38-
var accessories = [flagEmoji, .disclosureIndicator()]
40+
var accessories = [flagEmoji, .disclosureIndicator(), .outlineDisclosure()]
3941

4042
if self.person.isFavorite {
4143
let imageView = UIImageView(image: UIImage(systemName: "star.fill"))

Example/Sources/PersonModel/PersonModel.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct PersonModel: Hashable {
1919
let birthdate: Date
2020
let nationality: String
2121
var isFavorite = false
22+
var subPeople: [PersonModel] = []
2223

2324
var birthDateText: String {
2425
self.birthdate.formatted(date: .long, time: .omitted)
@@ -45,7 +46,13 @@ extension Date {
4546
extension PersonModel {
4647
static func makePeople() -> [PersonModel] {
4748
[
48-
PersonModel(name: "Noam Chomsky", birthdate: Date(year: 1_928, month: 12, day: 7), nationality: "🇺🇸"),
49+
PersonModel(name: "Noam Chomsky", birthdate: Date(year: 1_928, month: 12, day: 7), nationality: "🇺🇸", subPeople: [
50+
.init(name: "Steve Jobs", birthdate: Date(year: 1955, month: 2, day: 24), nationality: "🇺🇸", subPeople: [
51+
.init(name: "Another Steve Jobs", birthdate: Date(year: 1955, month: 2, day: 24), nationality: "🇺🇸", subPeople: [
52+
.init(name: "Yet Another Steve Jobs", birthdate: Date(year: 1955, month: 2, day: 24), nationality: "🇺🇸")
53+
])
54+
])
55+
]),
4956
PersonModel(name: "Emma Goldman", birthdate: Date(year: 1_869, month: 6, day: 27), nationality: "🇷🇺"),
5057
PersonModel(name: "Mikhail Bakunin", birthdate: Date(year: 1_814, month: 5, day: 30), nationality: "🇷🇺"),
5158
PersonModel(name: "Ursula K. Le Guin", birthdate: Date(year: 1_929, month: 10, day: 21), nationality: "🇺🇸"),

Sources/CollectionViewDriver.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -328,45 +328,45 @@ extension CollectionViewDriver: UICollectionViewDelegate {
328328
/// :nodoc:
329329
public func collectionView(_ collectionView: UICollectionView,
330330
shouldSelectItemAt indexPath: IndexPath) -> Bool {
331-
self.viewModel.cellViewModel(at: indexPath).shouldSelect
331+
self.viewModel.cellViewModel(at: indexPath, in: collectionView).shouldSelect
332332
}
333333

334334
/// :nodoc:
335335
public func collectionView(_ collectionView: UICollectionView,
336336
didSelectItemAt indexPath: IndexPath) {
337-
self.viewModel.cellViewModel(at: indexPath).didSelect(with: self._cellEventCoordinator)
337+
self.viewModel.cellViewModel(at: indexPath, in: collectionView).didSelect(with: self._cellEventCoordinator)
338338
}
339339

340340
/// :nodoc:
341341
public func collectionView(_ collectionView: UICollectionView,
342342
shouldDeselectItemAt indexPath: IndexPath) -> Bool {
343-
self.viewModel.cellViewModel(at: indexPath).shouldDeselect
343+
self.viewModel.cellViewModel(at: indexPath, in: collectionView).shouldDeselect
344344
}
345345

346346
/// :nodoc:
347347
public func collectionView(_ collectionView: UICollectionView,
348348
didDeselectItemAt indexPath: IndexPath) {
349-
self.viewModel.cellViewModel(at: indexPath).didDeselect(with: self._cellEventCoordinator)
349+
self.viewModel.cellViewModel(at: indexPath, in: collectionView).didDeselect(with: self._cellEventCoordinator)
350350
}
351351

352352
// MARK: Managing cell highlighting
353353

354354
/// :nodoc:
355355
public func collectionView(_ collectionView: UICollectionView,
356356
shouldHighlightItemAt indexPath: IndexPath) -> Bool {
357-
self.viewModel.cellViewModel(at: indexPath).shouldHighlight
357+
self.viewModel.cellViewModel(at: indexPath, in: collectionView).shouldHighlight
358358
}
359359

360360
/// :nodoc:
361361
public func collectionView(_ collectionView: UICollectionView,
362362
didHighlightItemAt indexPath: IndexPath) {
363-
self.viewModel.cellViewModel(at: indexPath).didHighlight()
363+
self.viewModel.cellViewModel(at: indexPath, in: collectionView).didHighlight()
364364
}
365365

366366
/// :nodoc:
367367
public func collectionView(_ collectionView: UICollectionView,
368368
didUnhighlightItemAt indexPath: IndexPath) {
369-
self.viewModel.cellViewModel(at: indexPath).didUnhighlight()
369+
self.viewModel.cellViewModel(at: indexPath, in: collectionView).didUnhighlight()
370370
}
371371

372372
// MARK: Managing context menus
@@ -375,7 +375,7 @@ extension CollectionViewDriver: UICollectionViewDelegate {
375375
public func collectionView(_ collectionView: UICollectionView,
376376
contextMenuConfigurationForItemAt indexPath: IndexPath,
377377
point: CGPoint) -> UIContextMenuConfiguration? {
378-
self.viewModel.cellViewModel(at: indexPath).contextMenuConfiguration
378+
self.viewModel.cellViewModel(at: indexPath, in: collectionView).contextMenuConfiguration
379379
}
380380

381381
// MARK: Tracking the addition and removal of views
@@ -384,7 +384,7 @@ extension CollectionViewDriver: UICollectionViewDelegate {
384384
public func collectionView(_ collectionView: UICollectionView,
385385
willDisplay cell: UICollectionViewCell,
386386
forItemAt indexPath: IndexPath) {
387-
self.viewModel._safeCellViewModel(at: indexPath)?.willDisplay()
387+
self.viewModel._safeCellViewModel(at: indexPath, in: collectionView)?.willDisplay()
388388
}
389389

390390
/// :nodoc:
@@ -399,7 +399,7 @@ extension CollectionViewDriver: UICollectionViewDelegate {
399399
public func collectionView(_ collectionView: UICollectionView,
400400
didEndDisplaying cell: UICollectionViewCell,
401401
forItemAt indexPath: IndexPath) {
402-
self.viewModel._safeCellViewModel(at: indexPath)?.didEndDisplaying()
402+
self.viewModel._safeCellViewModel(at: indexPath, in: collectionView)?.didEndDisplaying()
403403
}
404404

405405
/// :nodoc:

Sources/CollectionViewModel.swift

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,19 @@ public struct CollectionViewModel: DiffableViewModel {
6565

6666
// MARK: Accessing Cells
6767

68-
/// Returns the cell for the specified `id`.
68+
/// Returns the cell for the specified `id` by traversing each section and all of the children of each cell.
6969
///
7070
/// - Parameter id: The identifier for the cell.
7171
/// - Returns: The cell, if it exists.
7272
public func cellViewModel(for id: UniqueIdentifier) -> AnyCellViewModel? {
7373
for section in self.sections {
74-
for cell in section.cells where cell.id == id {
75-
return cell
74+
for cell in section.cells {
75+
if let foundViewModel = cellViewModel(in: cell, with: id) {
76+
return foundViewModel
77+
}
7678
}
7779
}
80+
7881
return nil
7982
}
8083

@@ -84,14 +87,42 @@ public struct CollectionViewModel: DiffableViewModel {
8487
/// - Returns: The cell at `indexPath`.
8588
///
8689
/// - Precondition: The specified `indexPath` must be valid.
87-
public func cellViewModel(at indexPath: IndexPath) -> AnyCellViewModel {
90+
public func cellViewModel(at indexPath: IndexPath, in collectionView: UICollectionView) -> AnyCellViewModel {
8891
precondition(indexPath.section < self.count)
8992
let section = self.sectionViewModel(at: indexPath.section)
9093

9194
let cells = section.cells
9295
precondition(indexPath.item < cells.count)
9396

94-
return cells[indexPath.item]
97+
guard let diffableDataSource = collectionView.dataSource as? DiffableDataSource
98+
else {
99+
return cells[indexPath.item]
100+
}
101+
102+
let snapshot = diffableDataSource.snapshot(for: section.id)
103+
let id = snapshot.visibleItems[indexPath.item]
104+
105+
guard let cellViewModel = cellViewModel(for: id)
106+
else {
107+
return cells[indexPath.item]
108+
}
109+
110+
return cellViewModel
111+
}
112+
113+
/// Recursively traverse the children array of each child to locate a matching cell view model
114+
private func cellViewModel(in viewModel: AnyCellViewModel, with id: UniqueIdentifier) -> AnyCellViewModel? {
115+
if viewModel.id == id {
116+
return viewModel
117+
}
118+
119+
for child in viewModel.children {
120+
if let foundChildViewModel = cellViewModel(in: child, with: id) {
121+
return foundChildViewModel
122+
}
123+
}
124+
125+
return nil
95126
}
96127

97128
// MARK: Accessing Supplementary Views
@@ -168,12 +199,12 @@ public struct CollectionViewModel: DiffableViewModel {
168199
return self.sectionViewModel(at: index)
169200
}
170201

171-
func _safeCellViewModel(at indexPath: IndexPath) -> AnyCellViewModel? {
202+
func _safeCellViewModel(at indexPath: IndexPath, in collectionView: UICollectionView) -> AnyCellViewModel? {
172203
guard let section = self._safeSectionViewModel(at: indexPath.section),
173204
indexPath.item < section.cells.count else {
174205
return nil
175206
}
176-
return self.cellViewModel(at: indexPath)
207+
return self.cellViewModel(at: indexPath, in: collectionView)
177208
}
178209

179210
func _safeSupplementaryViewModel(ofKind kind: String, at indexPath: IndexPath) -> AnySupplementaryViewModel? {

0 commit comments

Comments
 (0)