Skip to content

Commit

Permalink
clean up and improve example project
Browse files Browse the repository at this point in the history
  • Loading branch information
jessesquires committed Apr 15, 2024
1 parent d364ac4 commit 5938005
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 208 deletions.
14 changes: 1 addition & 13 deletions Example/ExampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
0B31B9932BCC6349006F2078 /* ColorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B31B9752BCC6349006F2078 /* ColorModel.swift */; };
0B31B9942BCC6349006F2078 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B31B9762BCC6349006F2078 /* Model.swift */; };
0B31B9952BCC6349006F2078 /* PersonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B31B9772BCC6349006F2078 /* PersonModel.swift */; };
0B31B9962BCC6349006F2078 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B31B9782BCC6349006F2078 /* ViewModel.swift */; };
0B31B9972BCC6349006F2078 /* FavoriteBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B31B97A2BCC6349006F2078 /* FavoriteBadgeView.swift */; };
0B31B9982BCC6349006F2078 /* FavoriteBadgeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B31B97B2BCC6349006F2078 /* FavoriteBadgeViewModel.swift */; };
0B31B9992BCC6349006F2078 /* FooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B31B97C2BCC6349006F2078 /* FooterView.swift */; };
Expand Down Expand Up @@ -97,7 +96,6 @@
0B31B9752BCC6349006F2078 /* ColorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorModel.swift; sourceTree = "<group>"; };
0B31B9762BCC6349006F2078 /* Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = "<group>"; };
0B31B9772BCC6349006F2078 /* PersonModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonModel.swift; sourceTree = "<group>"; };
0B31B9782BCC6349006F2078 /* ViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = "<group>"; };
0B31B97A2BCC6349006F2078 /* FavoriteBadgeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteBadgeView.swift; sourceTree = "<group>"; };
0B31B97B2BCC6349006F2078 /* FavoriteBadgeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavoriteBadgeViewModel.swift; sourceTree = "<group>"; };
0B31B97C2BCC6349006F2078 /* FooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -176,7 +174,6 @@
0B31B9492BCC62A5006F2078 /* LaunchScreen.storyboard */,
0B31B9442BCC62A4006F2078 /* Main.storyboard */,
0B31B9902BCC6349006F2078 /* Main */,
0B31B9792BCC6349006F2078 /* Models */,
0B31B9BD2BCC69A4006F2078 /* ColorModel */,
0B31B9BC2BCC6986006F2078 /* PersonModel */,
0B31B9892BCC6349006F2078 /* Grid */,
Expand All @@ -203,15 +200,6 @@
path = UITests;
sourceTree = "<group>";
};
0B31B9792BCC6349006F2078 /* Models */ = {
isa = PBXGroup;
children = (
0B31B9762BCC6349006F2078 /* Model.swift */,
0B31B9782BCC6349006F2078 /* ViewModel.swift */,
);
path = Models;
sourceTree = "<group>";
};
0B31B97E2BCC6349006F2078 /* SupplementaryViews */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -247,6 +235,7 @@
children = (
0B31B98A2BCC6349006F2078 /* AppDelegate.swift */,
0B31B98B2BCC6349006F2078 /* ExampleViewController.swift */,
0B31B9762BCC6349006F2078 /* Model.swift */,
0B31B98F2BCC6349006F2078 /* UIKit+Extensions.swift */,
);
path = Main;
Expand Down Expand Up @@ -470,7 +459,6 @@
0B31B9A32BCC6349006F2078 /* GridViewController.swift in Sources */,
0B31B9A22BCC6349006F2078 /* PersonCellViewModelGrid.swift in Sources */,
0B31B99E2BCC6349006F2078 /* GridColorCell.swift in Sources */,
0B31B9962BCC6349006F2078 /* ViewModel.swift in Sources */,
0B31B9A92BCC6349006F2078 /* UIKit+Extensions.swift in Sources */,
0B31B9982BCC6349006F2078 /* FavoriteBadgeViewModel.swift in Sources */,
0B31B99F2BCC6349006F2078 /* ColorCellViewModelGrid.swift in Sources */,
Expand Down
2 changes: 0 additions & 2 deletions Example/Sources/ColorModel/ColorCellViewModelList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ struct ColorCellViewModelList: CellViewModel {

var id: UniqueIdentifier { self.color.id }

let shouldHighlight = false

let contextMenuConfiguration: UIContextMenuConfiguration?

func configure(cell: UICollectionViewListCell) {
Expand Down
1 change: 0 additions & 1 deletion Example/Sources/ColorModel/ColorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ final class ColorViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white

self.label.font = UIFont.preferredFont(forTextStyle: .title1)
self.label.textAlignment = .center
Expand Down
115 changes: 103 additions & 12 deletions Example/Sources/Grid/GridViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,56 @@
import ReactiveCollectionsKit
import UIKit

final class GridViewController: ExampleViewController {
final class GridViewController: ExampleViewController, CellEventCoordinator {

override var model: Model {
didSet {
self.driver.viewModel = self.createCollectionViewModel(style: .grid)
// Every time the model updates, regenerate and set the view model
self.driver.viewModel = self.makeCollectionViewModel()
}
}

// MARK: CellEventCoordinator

func didSelectCell(viewModel: any CellViewModel) {
print("\(#function): \(viewModel.id)")

if let personVM = viewModel as? PersonCellViewModelGrid {
let personVC = PersonViewController(person: personVM.person)
self.navigationController?.pushViewController(personVC, animated: true)
return
}

if let colorVM = viewModel as? ColorCellViewModelGrid {
let colorVC = ColorViewController(color: colorVM.color)
self.navigationController?.pushViewController(colorVC, animated: true)
return
}

assertionFailure("unhandled cell selection")
}

// MARK: View Lifecycle

override func viewDidLoad() {
super.viewDidLoad()

let layout = self.makeLayout()
let viewModel = self.makeCollectionViewModel()

self.driver = CollectionViewDriver(
view: self.collectionView,
layout: layout,
viewModel: viewModel,
cellEventCoordinator: self,
animateUpdates: true
) { [unowned self] in
print("grid did update!")
print(self.driver.viewModel)
}
}

func makeLayout() -> UICollectionViewCompositionalLayout {
let fractionalWidth = CGFloat(0.5)
let inset = CGFloat(4)

Expand Down Expand Up @@ -65,18 +104,70 @@ final class GridViewController: ExampleViewController {
section.contentInsets = NSDirectionalEdgeInsets(top: inset, leading: inset, bottom: inset, trailing: inset)
section.boundarySupplementaryItems = [sectionHeader, sectionFooter]

let layout = UICollectionViewCompositionalLayout(section: section)
return UICollectionViewCompositionalLayout(section: section)
}

let viewModel = self.createCollectionViewModel(style: .grid)
func makeCollectionViewModel() -> CollectionViewModel {
// Create people section
let peopleCellViewModels = self.model.people.map {
let menuConfig = UIContextMenuConfiguration.configFor(
itemId: $0.id,
favoriteAction: { [unowned self] in
self.toggleFavorite(id: $0)
},
deleteAction: { [unowned self] in
self.deleteItem(id: $0)
}
)

self.driver = CollectionViewDriver(
view: self.collectionView,
layout: layout,
viewModel: viewModel,
cellEventCoordinator: self,
animateUpdates: true) {
print("grid did update!")
print(self.driver.viewModel)
return PersonCellViewModelGrid(
person: $0,
contextMenuConfiguration: menuConfig
).anyViewModel
}
let peopleHeader = HeaderViewModel(title: "People", style: .large)
let peopleFooter = FooterViewModel(text: "\(self.model.people.count) people")
let peopleFavoriteBadges = self.model.people.compactMap {
FavoriteBadgeViewModel(isHidden: !$0.isFavorite, id: $0.name + "_badge")
}
let peopleSection = SectionViewModel(
id: "section_people_grid",
cells: peopleCellViewModels,
header: peopleHeader,
footer: peopleFooter,
supplementaryViews: peopleFavoriteBadges
)

// Create color section
let colorCellViewModels = self.model.colors.map {
let menuConfig = UIContextMenuConfiguration.configFor(
itemId: $0.id,
favoriteAction: { [unowned self] in
self.toggleFavorite(id: $0)
},
deleteAction: { [unowned self] in
self.deleteItem(id: $0)
}
)
return ColorCellViewModelGrid(
color: $0,
contextMenuConfiguration: menuConfig
).anyViewModel
}
let colorHeader = HeaderViewModel(title: "Colors", style: .large)
let colorFooter = FooterViewModel(text: "\(self.model.colors.count) colors")
let colorFavoriteBadges = self.model.colors.compactMap {
FavoriteBadgeViewModel(isHidden: !$0.isFavorite, id: $0.name + "_badge")
}
let colorSection = SectionViewModel(
id: "section_colors_grid",
cells: colorCellViewModels,
header: colorHeader,
footer: colorFooter,
supplementaryViews: colorFavoriteBadges
)

// Create final view model
return CollectionViewModel(sections: [peopleSection, colorSection])
}
}
121 changes: 101 additions & 20 deletions Example/Sources/List/ListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,78 @@ import Combine
import ReactiveCollectionsKit
import UIKit

final class ListViewController: ExampleViewController {
final class ListViewController: ExampleViewController, CellEventCoordinator {

var cancellables = [AnyCancellable]()

override var model: Model {
didSet {
self.driver.viewModel = self.createCollectionViewModel(style: .list)
// Every time the model updates, regenerate and set the view model
self.driver.viewModel = self.makeCollectionViewModel()
}
}

// MARK: CellEventCoordinator

func didSelectCell(viewModel: any CellViewModel) {
print("\(#function): \(viewModel.id)")

if let personVM = viewModel as? PersonCellViewModelList {
let personVC = PersonViewController(person: personVM.person)
self.navigationController?.pushViewController(personVC, animated: true)
return
}

if let colorVM = viewModel as? ColorCellViewModelList {
let colorVC = ColorViewController(color: colorVM.color)
self.navigationController?.pushViewController(colorVC, animated: true)
return
}

assertionFailure("unhandled cell selection")
}

// MARK: View lifecycle

override func viewDidLoad() {
super.viewDidLoad()

let layout = UICollectionViewCompositionalLayout { _, layoutEnvironment in
let layout = self.makeLayout()
let viewModel = self.makeCollectionViewModel()

self.driver = CollectionViewDriver(
view: self.collectionView,
layout: layout,
viewModel: viewModel,
cellEventCoordinator: self,
animateUpdates: true
) { [unowned self] in
print("list did update!")
print(self.driver.viewModel)
}

self.driver.$viewModel
.sink { _ in
print("did publish view model update")
}
.store(in: &self.cancellables)

}

func makeLayout() -> UICollectionViewCompositionalLayout {
UICollectionViewCompositionalLayout { _, layoutEnvironment in
var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
configuration.headerMode = .supplementary
configuration.footerMode = .supplementary

// TODO: swipe actions broken. actually need to reference item identifier
// TODO: swipe actions need re-working. need to reference item identifier, not index path.
// configuration.leadingSwipeActionsConfigurationProvider = { [unowned self] indexPath in
// let favoriteAction = UIContextualAction(style: .normal, title: "Favorite") { _, _, completion in
// // self.toggleFavoriteAt(indexPath: indexPath)
// completion(true)
// }
// favoriteAction.image = UIImage(systemName: "star.fill")
// favoriteAction.backgroundColor = .systemYellow
//
// return UISwipeActionsConfiguration(actions: [favoriteAction])
// }
//
Expand All @@ -52,7 +97,6 @@ final class ListViewController: ExampleViewController {
// }
// deleteAction.image = UIImage(systemName: "trash")
// deleteAction.backgroundColor = .systemRed
//
// return UISwipeActionsConfiguration(actions: [deleteAction])
// }

Expand All @@ -61,24 +105,61 @@ final class ListViewController: ExampleViewController {
layoutEnvironment: layoutEnvironment
)
}
}

let viewModel = self.createCollectionViewModel(style: .list)
func makeCollectionViewModel() -> CollectionViewModel {
// Create People Section
let peopleCellViewModels = self.model.people.map {
let menuConfig = UIContextMenuConfiguration.configFor(
itemId: $0.id,
favoriteAction: { [unowned self] in
self.toggleFavorite(id: $0)
},
deleteAction: { [unowned self] in
self.deleteItem(id: $0)
}
)

self.driver = CollectionViewDriver(
view: self.collectionView,
layout: layout,
viewModel: viewModel,
cellEventCoordinator: self,
animateUpdates: true) { [unowned self] in
print("list did update!")
print(self.driver.viewModel)
return PersonCellViewModelList(
person: $0,
contextMenuConfiguration: menuConfig
).anyViewModel
}
let peopleHeader = HeaderViewModel(title: "People", style: .small)
let peopleFooter = FooterViewModel(text: "\(self.model.people.count) people")
let peopleSection = SectionViewModel(
id: "section_people_list",
cells: peopleCellViewModels,
header: peopleHeader,
footer: peopleFooter
)

self.driver.$viewModel
.sink { _ in
print("did publish view model update")
}
.store(in: &self.cancellables)
// Create Color Section
let colorCellViewModels = self.model.colors.map {
let menuConfig = UIContextMenuConfiguration.configFor(
itemId: $0.id,
favoriteAction: { [unowned self] in
self.toggleFavorite(id: $0)
},
deleteAction: { [unowned self] in
self.deleteItem(id: $0)
}
)
return ColorCellViewModelList(
color: $0,
contextMenuConfiguration: menuConfig
).anyViewModel
}
let colorHeader = HeaderViewModel(title: "Colors", style: .small)
let colorFooter = FooterViewModel(text: "\(self.model.colors.count) colors")
let colorSection = SectionViewModel(
id: "section_colors_list",
cells: colorCellViewModels,
header: colorHeader,
footer: colorFooter
)

// Create final view model
return CollectionViewModel(sections: [peopleSection, colorSection])
}
}
Loading

0 comments on commit 5938005

Please sign in to comment.