From 1ece8fcad4620185a7e79b02b3a75b5a0400d856 Mon Sep 17 00:00:00 2001 From: Jesse Squires Date: Sun, 12 Sep 2021 22:26:59 -0700 Subject: [PATCH] implement initial context menus in example project delete works, favorite does not (yet) #1 --- .../Sources/Grid/GridColorCellViewModel.swift | 4 +- .../Grid/GridPersonCellViewModel.swift | 4 +- Example/Sources/Grid/GridViewController.swift | 4 +- .../Sources/List/ListColorCellViewModel.swift | 4 +- .../List/ListPersonCellViewModel.swift | 4 +- Example/Sources/List/ListViewController.swift | 4 +- .../ExampleCollectionViewController.swift | 19 ++++++++ Example/Sources/Main/UIKit+Extensions.swift | 25 ++++++++++ Example/Sources/Models/Model.swift | 19 ++++++++ Example/Sources/Models/ViewModel.swift | 47 ++++++++++++++----- 10 files changed, 114 insertions(+), 20 deletions(-) diff --git a/Example/Sources/Grid/GridColorCellViewModel.swift b/Example/Sources/Grid/GridColorCellViewModel.swift index e57fe20..c9ff1e1 100644 --- a/Example/Sources/Grid/GridColorCellViewModel.swift +++ b/Example/Sources/Grid/GridColorCellViewModel.swift @@ -19,10 +19,12 @@ struct GridColorCellViewModel: CellViewModel { // MARK: CellViewModel - var id: UniqueIdentifier { self.color.name } + var id: UniqueIdentifier { self.color.id } let shouldHighlight = false + let contextMenuConfiguration: UIContextMenuConfiguration? + func configure(cell: GridColorCell) { cell.label.text = self.color.name cell.backgroundColor = self.color.uiColor diff --git a/Example/Sources/Grid/GridPersonCellViewModel.swift b/Example/Sources/Grid/GridPersonCellViewModel.swift index 950495f..135dac9 100644 --- a/Example/Sources/Grid/GridPersonCellViewModel.swift +++ b/Example/Sources/Grid/GridPersonCellViewModel.swift @@ -19,7 +19,9 @@ struct GridPersonCellViewModel: CellViewModel { // MARK: CellViewModel - var id: UniqueIdentifier { self.person.name } + var id: UniqueIdentifier { self.person.id } + + let contextMenuConfiguration: UIContextMenuConfiguration? var nibName: String? { "GridPersonCell" } diff --git a/Example/Sources/Grid/GridViewController.swift b/Example/Sources/Grid/GridViewController.swift index f909083..beec8aa 100644 --- a/Example/Sources/Grid/GridViewController.swift +++ b/Example/Sources/Grid/GridViewController.swift @@ -18,7 +18,7 @@ final class GridViewController: ExampleCollectionViewController { override var model: Model { didSet { - self.driver.viewModel = ViewModel.createGrid(from: self.model) + self.driver.viewModel = self.createCollectionViewModel(style: .grid) } } @@ -67,7 +67,7 @@ final class GridViewController: ExampleCollectionViewController { let layout = UICollectionViewCompositionalLayout(section: section) - let viewModel = ViewModel.createGrid(from: self.model) + let viewModel = self.createCollectionViewModel(style: .grid) self.driver = CollectionViewDriver( view: self.collectionView, diff --git a/Example/Sources/List/ListColorCellViewModel.swift b/Example/Sources/List/ListColorCellViewModel.swift index b713787..90668cf 100644 --- a/Example/Sources/List/ListColorCellViewModel.swift +++ b/Example/Sources/List/ListColorCellViewModel.swift @@ -19,10 +19,12 @@ struct ListColorCellViewModel: CellViewModel { // MARK: CellViewModel - var id: UniqueIdentifier { self.color.name } + var id: UniqueIdentifier { self.color.id } let shouldHighlight = false + let contextMenuConfiguration: UIContextMenuConfiguration? + func configure(cell: UICollectionViewListCell) { var contentConfiguration = cell.defaultContentConfiguration() contentConfiguration.text = self.color.name diff --git a/Example/Sources/List/ListPersonCellViewModel.swift b/Example/Sources/List/ListPersonCellViewModel.swift index 8cfb832..aef31e0 100644 --- a/Example/Sources/List/ListPersonCellViewModel.swift +++ b/Example/Sources/List/ListPersonCellViewModel.swift @@ -19,7 +19,9 @@ struct ListPersonCellViewModel: CellViewModel { // MARK: CellViewModel - var id: UniqueIdentifier { self.person.name } + var id: UniqueIdentifier { self.person.id } + + let contextMenuConfiguration: UIContextMenuConfiguration? func configure(cell: UICollectionViewListCell) { var contentConfiguration = UIListContentConfiguration.subtitleCell() diff --git a/Example/Sources/List/ListViewController.swift b/Example/Sources/List/ListViewController.swift index 3106309..5d10062 100644 --- a/Example/Sources/List/ListViewController.swift +++ b/Example/Sources/List/ListViewController.swift @@ -18,7 +18,7 @@ final class ListViewController: ExampleCollectionViewController { override var model: Model { didSet { - self.driver.viewModel = ViewModel.createList(from: self.model) + self.driver.viewModel = self.createCollectionViewModel(style: .list) } } @@ -57,7 +57,7 @@ final class ListViewController: ExampleCollectionViewController { return section } - let viewModel = ViewModel.createList(from: self.model) + let viewModel = self.createCollectionViewModel(style: .list) self.driver = CollectionViewDriver( view: self.collectionView, diff --git a/Example/Sources/Main/ExampleCollectionViewController.swift b/Example/Sources/Main/ExampleCollectionViewController.swift index cf9cab2..17b2253 100644 --- a/Example/Sources/Main/ExampleCollectionViewController.swift +++ b/Example/Sources/Main/ExampleCollectionViewController.swift @@ -43,6 +43,10 @@ class ExampleCollectionViewController: UICollectionViewController { self.model = Model() } + func deleteItem(id: UniqueIdentifier) { + self.model.delete(id: id) + } + func deleteAt(indexPath: IndexPath) { self.model.deleteModelAt(indexPath: indexPath) } @@ -51,8 +55,23 @@ class ExampleCollectionViewController: UICollectionViewController { self.model.toggleFavoriteAt(indexPath: indexPath) } + func toggleFavorite(id: UniqueIdentifier) { + self.model.toggleFavorite(id: id) + } + // MARK: Helpers + func createCollectionViewModel(style: ViewModelStyle) -> CollectionViewModel { + ViewModel.create( + model: self.model, + style: style, + favoriteAction: { [unowned self] in + self.toggleFavorite(id: $0) + }, deleteAction: { [unowned self] in + self.deleteItem(id: $0) + }) + } + private func appendRightBarButton(_ item: UIBarButtonItem) { var items = self.navigationItem.rightBarButtonItems ?? [] items.append(item) diff --git a/Example/Sources/Main/UIKit+Extensions.swift b/Example/Sources/Main/UIKit+Extensions.swift index ad2c990..6e6b8ff 100644 --- a/Example/Sources/Main/UIKit+Extensions.swift +++ b/Example/Sources/Main/UIKit+Extensions.swift @@ -11,6 +11,7 @@ // Copyright © 2019-present Jesse Squires // +import ReactiveCollectionsKit import UIKit extension UIBarButtonItem { @@ -21,3 +22,27 @@ extension UIBarButtonItem { action: action) } } + +extension UIContextMenuConfiguration { + typealias ItemAction = (UniqueIdentifier) -> Void + + static func configFor( + itemId: UniqueIdentifier, + favoriteAction: @escaping ItemAction, + deleteAction: @escaping ItemAction) -> UIContextMenuConfiguration? { + UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in + let favorite = UIAction(title: "Favorite", + image: UIImage(systemName: "star.fill")) { _ in + favoriteAction(itemId) + } + + let delete = UIAction(title: "Delete", + image: UIImage(systemName: "trash"), + attributes: .destructive) { _ in + deleteAction(itemId) + } + + return UIMenu(children: [favorite, delete]) + } + } +} diff --git a/Example/Sources/Models/Model.swift b/Example/Sources/Models/Model.swift index 6bb507d..4a15298 100644 --- a/Example/Sources/Models/Model.swift +++ b/Example/Sources/Models/Model.swift @@ -12,6 +12,7 @@ // import Foundation +import ReactiveCollectionsKit struct Model { private(set) var people = PersonModel.makePeople() @@ -36,6 +37,15 @@ struct Model { } } + mutating func delete(id: UniqueIdentifier) { + if let index = self.people.firstIndex(where: { $0.id == id }) { + self.people.remove(at: index) + } + if let index = self.colors.firstIndex(where: { $0.id == id }) { + self.colors.remove(at: index) + } + } + mutating func toggleFavoriteAt(indexPath: IndexPath) { switch indexPath.section { case 0: @@ -48,6 +58,15 @@ struct Model { fatalError("invalid indexPath: \(indexPath)") } } + + mutating func toggleFavorite(id: UniqueIdentifier) { + if let index = self.people.firstIndex(where: { $0.id == id }) { + self.people[index].isFavorite.toggle() + } + if let index = self.colors.firstIndex(where: { $0.id == id }) { + self.colors[index].isFavorite.toggle() + } + } } extension Model: CustomDebugStringConvertible { diff --git a/Example/Sources/Models/ViewModel.swift b/Example/Sources/Models/ViewModel.swift index ee0ab63..2e2bb05 100644 --- a/Example/Sources/Models/ViewModel.swift +++ b/Example/Sources/Models/ViewModel.swift @@ -13,6 +13,7 @@ import Foundation import ReactiveCollectionsKit +import UIKit enum ViewModelStyle: String { case grid @@ -30,25 +31,35 @@ enum ViewModelStyle: String { } enum ViewModel { - static func createGrid(from model: Model) -> CollectionViewModel { - self.create(model: model, style: .grid) - } - - static func createList(from model: Model) -> CollectionViewModel { - self.create(model: model, style: .list) - } + typealias ItemAction = (UniqueIdentifier) -> Void - private static func create(model: Model, style: ViewModelStyle) -> CollectionViewModel { + static func create( + model: Model, + style: ViewModelStyle, + favoriteAction: @escaping ItemAction, + deleteAction: @escaping ItemAction) -> CollectionViewModel { // MARK: People Section let peopleCellViewModels: [AnyCellViewModel] = model.people.map { + let menuConfig = UIContextMenuConfiguration.configFor( + itemId: $0.id, + favoriteAction: favoriteAction, + deleteAction: deleteAction + ) + switch style { case .grid: - return GridPersonCellViewModel(person: $0).anyViewModel + return GridPersonCellViewModel( + person: $0, + contextMenuConfiguration: menuConfig + ).anyViewModel case .list: - return ListPersonCellViewModel(person: $0).anyViewModel + return ListPersonCellViewModel( + person: $0, + contextMenuConfiguration: menuConfig + ).anyViewModel } } @@ -72,12 +83,24 @@ enum ViewModel { // MARK: Color Section let colorCellViewModels: [AnyCellViewModel] = model.colors.map { + let menuConfig = UIContextMenuConfiguration.configFor( + itemId: $0.id, + favoriteAction: favoriteAction, + deleteAction: deleteAction + ) + switch style { case .grid: - return GridColorCellViewModel(color: $0).anyViewModel + return GridColorCellViewModel( + color: $0, + contextMenuConfiguration: menuConfig + ).anyViewModel case .list: - return ListColorCellViewModel(color: $0).anyViewModel + return ListColorCellViewModel( + color: $0, + contextMenuConfiguration: menuConfig + ).anyViewModel } }