fix: remove excessive level of an abstraction

This commit is contained in:
Nikita Semenov 2022-08-03 19:57:33 +03:00
parent 76f6635cbc
commit c6d17e96ab
21 changed files with 398 additions and 454 deletions

View File

@ -24,27 +24,33 @@ import UIKit
@available(iOS 13, *)
public extension UICollectionViewLayout {
static func makeHorizontalLayout(_ conf: FiltersLayoutConfiguration) -> UICollectionViewLayout {
static func gridLayout(_ conf: FiltersLayoutConfiguration) -> UICollectionViewLayout {
let item = NSCollectionLayoutItem(layoutSize: conf.itemSize)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: conf.groupSize, subitems: [item])
group.interItemSpacing = conf.itemSpacing
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: conf.itemSize.heightDimension)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
group.interItemSpacing = .fixed(conf.horizontalItemSpacing)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(insets: conf.contentInsets)
section.interGroupSpacing = conf.groupSpacing
section.interGroupSpacing = conf.verticalItemSpacing
return UICollectionViewCompositionalLayout(section: section)
}
static func makeHorizontalScrollLayout(_ conf: FiltersLayoutConfiguration) -> UICollectionViewLayout {
static func horizontalScrollLayout(_ conf: FiltersLayoutConfiguration) -> UICollectionViewLayout {
let item = NSCollectionLayoutItem(layoutSize: conf.itemSize)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: conf.groupSize, subitems: [item])
group.interItemSpacing = conf.itemSpacing
let groupSize = NSCollectionLayoutSize(widthDimension: conf.itemSize.widthDimension,
heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
group.interItemSpacing = .fixed(conf.horizontalItemSpacing)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(insets: conf.contentInsets)
section.interGroupSpacing = conf.groupSpacing
section.interGroupSpacing = conf.verticalItemSpacing
section.orthogonalScrollingBehavior = .continuous
return UICollectionViewCompositionalLayout(section: section)

View File

@ -32,12 +32,12 @@ public struct DefaultFilterPropertyValue: FilterPropertyValueRepresenter,
public var isSelected: Bool
public func convertToViewModel() -> CellViewModelRepresentable {
DefaultCellViewModel(id: id,
title: title,
selectedColor: .green,
insets: .init(top: .zero, left: 8, bottom: .zero, right: 8),
isSelected: isSelected)
public func convertToViewModel() -> FilterCellViewModelRepresentable {
DefaultFilterCellViewModel(id: id,
title: title,
selectedColor: .green,
insets: .init(top: .zero, left: 8, bottom: .zero, right: 8),
isSelected: isSelected)
}
}

View File

@ -22,62 +22,38 @@
import UIKit
@available(iOS 13, *)
public enum FiltersCollectionViewLayout: CollectionViewLayoutProtocol {
case horizontalScroll(FiltersLayoutConfiguration?)
case horizontal(FiltersLayoutConfiguration?)
case custom(UICollectionViewLayout)
public var collectionViewLayout: UICollectionViewLayout {
switch self {
case let .horizontalScroll(configuration):
return .makeHorizontalScrollLayout(configuration ?? FiltersLayoutConfiguration.horizontalScrollConfiguration)
case let .horizontal(configuration):
return .makeHorizontalLayout(configuration ?? FiltersLayoutConfiguration.horizontalConfiguration)
case let .custom(layout):
return layout
}
}
}
@available(iOS 13, *)
public struct FiltersLayoutConfiguration {
public var itemSize: NSCollectionLayoutSize
public var groupSize: NSCollectionLayoutSize
public var itemSpacing: NSCollectionLayoutSpacing
public var groupSpacing: CGFloat
public var horizontalItemSpacing: CGFloat
public var verticalItemSpacing: CGFloat
public var contentInsets: UIEdgeInsets
public init(itemSize: NSCollectionLayoutSize = .init(widthDimension: .estimated(32),
heightDimension: .estimated(32)),
groupSize: NSCollectionLayoutSize = .init(widthDimension: .fractionalWidth(1),
heightDimension: .estimated(32)),
itemSpacing: NSCollectionLayoutSpacing = .fixed(.zero),
groupSpacing: CGFloat,
horizontalItemSpacing: CGFloat = .zero,
verticalItemSpacing: CGFloat = .zero,
contentInsets: UIEdgeInsets) {
self.itemSize = itemSize
self.groupSize = groupSize
self.itemSpacing = itemSpacing
self.groupSpacing = groupSpacing
self.horizontalItemSpacing = horizontalItemSpacing
self.verticalItemSpacing = verticalItemSpacing
self.contentInsets = contentInsets
}
}
@available(iOS 13, *)
extension FiltersLayoutConfiguration {
static let horizontalScrollConfiguration = FiltersLayoutConfiguration(itemSpacing: .fixed(8),
groupSpacing: 16,
static let horizontalScrollConfiguration = FiltersLayoutConfiguration(horizontalItemSpacing: 16,
contentInsets: .init(top: .zero,
left: 8,
bottom: .zero,
right: 8))
static let horizontalConfiguration = FiltersLayoutConfiguration(itemSpacing: .fixed(8),
groupSpacing: 16,
contentInsets: .init(top: .zero,
left: 8,
bottom: .zero,
right: 8))
static let gridConfiguration = FiltersLayoutConfiguration(verticalItemSpacing: 16,
contentInsets: .init(top: .zero,
left: 8,
bottom: .zero,
right: 8))
}

View File

@ -22,15 +22,15 @@
import UIKit
public protocol CollectionDirectorRepresenter: UICollectionViewDataSource, UICollectionViewDelegate {
associatedtype Item
var collectionView: UICollectionView? { get set }
var delegate: FilterItemsDelegate? { get set }
func insertItem(_ item: Item, at index: Int)
func deleteItem(at index: Int)
func update(item: Item, at index: Int)
func scrollToItem(at indexPath: IndexPath, animated: Bool)
}
//public protocol CollectionDirectorRepresenter: UICollectionViewDataSource, UICollectionViewDelegate {
//
// associatedtype Item
//
// var collectionView: UICollectionView? { get set }
// var delegate: FilterItemsDelegate? { get set }
//
// func insertItem(_ item: Item, at index: Int)
// func deleteItem(at index: Int)
// func update(item: Item, at index: Int)
// func scrollToItem(at indexPath: IndexPath, animated: Bool)
//}

View File

@ -19,31 +19,31 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import TIUIKitCore
import UIKit
public protocol FilterCollectionItem {
associatedtype Filter: FilterPropertyValueRepresenter
var identifier: String { get }
var itemType: AnyClass { get }
var filter: Filter { get set }
init(filter: Filter, viewModel: CellViewModelRepresentable)
func register(for collectionView: UICollectionView)
func configure(item: UICollectionViewCell)
func didSelectItem(atIndexPath indexPath: IndexPath, cell: UICollectionViewCell?)
}
public extension FilterCollectionItem {
var identifier: String {
filter.id
}
func register(for collectionView: UICollectionView) {
collectionView.register(itemType, forCellWithReuseIdentifier: identifier)
}
}
//
//import TIUIKitCore
//import UIKit
//
//public protocol FilterCollectionItem {
//
// associatedtype Filter: FilterPropertyValueRepresenter
//
// var identifier: String { get }
// var itemType: AnyClass { get }
// var filter: Filter { get set }
//
// init(filter: Filter, viewModel: FilterCellViewModelRepresentable)
//
// func register(for collectionView: UICollectionView)
// func configure(item: UICollectionViewCell)
// func didSelectItem(atIndexPath indexPath: IndexPath, cell: UICollectionViewCell?)
//}
//
//public extension FilterCollectionItem {
// var identifier: String {
// filter.id
// }
//
// func register(for collectionView: UICollectionView) {
// collectionView.register(itemType, forCellWithReuseIdentifier: identifier)
// }
//}

View File

@ -21,7 +21,7 @@
//
import UIKit
public protocol FilterItemsDelegate: AnyObject {
func didSelectItem(atIndexPath indexPath: IndexPath)
}
//
//public protocol FilterItemsDelegate: AnyObject {
// func didSelectItem(atIndexPath indexPath: IndexPath)
//}

View File

@ -20,14 +20,14 @@
// THE SOFTWARE.
//
public protocol FilterPropertyValueRepresenter: CellViewModelConvertable {
public protocol FilterPropertyValueRepresenter: FilterCellViewModelConvertable {
var id: String { get }
var excludingProperties: [String]? { get }
var isSelected: Bool { get set }
}
public protocol CellViewModelRepresentable { }
public protocol FilterCellViewModelRepresentable { }
public protocol CellViewModelConvertable {
func convertToViewModel() -> CellViewModelRepresentable
public protocol FilterCellViewModelConvertable {
func convertToViewModel() -> FilterCellViewModelRepresentable
}

View File

@ -24,10 +24,11 @@ import UIKit
public protocol FiltersCollectionHolder: AnyObject {
var collectionView: UICollectionView { get set }
var collectionView: UICollectionView { get }
var viewModel: DefaultFiltersViewModel? { get set }
func select(_ items: [FilterPropertyValueRepresenter])
func deselect(_ items: [FilterPropertyValueRepresenter])
func applyChange(_ changes: [DefaultFiltersViewModel.Change])
func updateView()
func configure(filterCell: UICollectionViewCell, cellViewModel: FilterCellViewModelProtocol)
func registerCells()
}

View File

@ -31,54 +31,51 @@ public protocol FiltersViewModelProtocol: AnyObject {
var filtersCollectionHolder: FiltersCollectionHolder? { get set }
func filterItem(atIndexPath indexPath: IndexPath)
func toggleFilter(atIndexPath indexPath: IndexPath) -> (selected: [Filter], deselected: [Filter])
}
public extension FiltersViewModelProtocol {
func filterItem(atIndexPath indexPath: IndexPath) {
guard let item = getItemSafely(indexPath.item) else { return }
func toggleFilter(atIndexPath indexPath: IndexPath) -> (selected: [Filter], deselected: [Filter]) {
guard let item = getItemSafely(indexPath.item) else { return ([], []) }
let (s, d) = filterItem(item)
filtersCollectionHolder?.select(s)
filtersCollectionHolder?.deselect(d)
filtersCollectionHolder?.updateView()
return toggleFilter(item)
}
@discardableResult
private func filterItem(_ item: Filter) -> (selected: [Filter], deselected: [Filter]) {
var itemsToDeselect = [Filter]()
var itemsToSelect = [Filter]()
let selectedItem = selectedFilters.first { selectedItem in
selectedItem.id == item.id
private func toggleFilter(_ filter: Filter) -> (selected: [Filter], deselected: [Filter]) {
var filtersToDeselect = [Filter]()
var filtersToSelect = [Filter]()
let selectedFilter = selectedFilters.first { selectedFilter in
selectedFilter.id == filter.id
}
if let selectedItem = selectedItem {
selectedFilters.remove(selectedItem)
itemsToDeselect.append(item)
if let selectedFilter = selectedFilter {
selectedFilters.remove(selectedFilter)
filtersToDeselect.append(filter)
} else {
selectedFilters.insert(item)
itemsToSelect.append(item)
selectedFilters.insert(filter)
filtersToSelect.append(filter)
if let itemsToExclude = item.excludingProperties, !itemsToExclude.isEmpty {
for itemIdToExclude in itemsToExclude {
let itemToExclude = selectedFilters.first { item in
item.id == itemIdToExclude
if let filtersToExclude = filter.excludingProperties, !filtersToExclude.isEmpty {
for filtersIdToExclude in filtersToExclude {
let filterToExclude = selectedFilters.first { filter in
filter.id == filtersIdToExclude
}
if let itemToExclude = itemToExclude {
let (_, deselected) = filterItem(itemToExclude)
itemsToDeselect.append(contentsOf: deselected)
if let itemToExclude = filterToExclude {
let (_, deselected) = toggleFilter(itemToExclude)
filtersToDeselect.append(contentsOf: deselected)
}
}
}
}
return (itemsToSelect, itemsToDeselect)
return (filtersToSelect, filtersToDeselect)
}
private func getItemSafely(_ index: Int) -> Filter? {
guard index >= 0 && index <= filters.count else {
guard index >= 0 && index < filters.count else {
return nil
}

View File

@ -22,7 +22,7 @@
import UIKit
public protocol CellViewModelProtocol: CellViewModelRepresentable {
public protocol FilterCellViewModelProtocol: FilterCellViewModelRepresentable {
var id: String { get set }
var title: String { get set }
var selectedColor: UIColor { get set }
@ -30,7 +30,7 @@ public protocol CellViewModelProtocol: CellViewModelRepresentable {
var isSelected: Bool { get set }
}
public struct DefaultCellViewModel: CellViewModelProtocol, CellViewModelRepresentable {
public struct DefaultFilterCellViewModel: FilterCellViewModelProtocol, FilterCellViewModelRepresentable {
public var id: String
public var title: String
public var selectedColor: UIColor

View File

@ -0,0 +1,128 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
//open class DefaultFiltersCollectionDirector<FilterItem: FilterCollectionItem>: NSObject,
// CollectionDirectorRepresenter,
// UICollectionViewDataSource,
// UICollectionViewDelegate {
//
// private var _collectionItems: [FilterItem] = []
//
// public weak var collectionView: UICollectionView?
// public weak var delegate: FilterItemsDelegate?
//
// public var collectionItems: [FilterItem] {
// get {
// _collectionItems
// }
// set {
// _collectionItems = newValue
// collectionView?.reloadData()
// }
// }
//
// public init(collectionView: UICollectionView) {
// super.init()
//
// bind(to: collectionView)
// }
//
// public func insertItem(_ item: FilterItem, at index: Int) {
// collectionItems.insert(item, at: index)
//
// let indexPath = IndexPath(row: index, section: .zero)
//
// guard collectionItems.count != 1 else {
// return
// }
//
// scrollToItem(at: indexPath)
// }
//
// public func deleteItem(at index: Int) {
// collectionItems.remove(at: index)
//
// let indexPath = IndexPath(row: max(index - 1, .zero), section: .zero)
// scrollToItem(at: indexPath)
// }
//
// public func update(item: FilterItem, at index: Int) {
// _collectionItems[index] = item
// }
//
// public func scrollToItem(at indexPath: IndexPath, animated: Bool = true) {
// collectionView?.performBatchUpdates(nil, completion: { [weak self] isReady in
// if isReady {
// self?.collectionView?.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: animated)
// }
// })
// }
//
// // MARK: - UICollectionViewDataSource
//
// public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// collectionItems.forEach { $0.register(for: collectionView) }
// return collectionItems.count
// }
//
// public func collectionView(_ collectionView: UICollectionView,
// cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//
// let viewModel = collectionItems[indexPath.item]
// let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.identifier, for: indexPath)
// viewModel.configure(item: cell)
// return cell
// }
//
// // MARK: - UICollectionViewDelegate
//
// public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// let cell = collectionView.cellForItem(at: indexPath)
// didSelectItem(atIndexPath: indexPath, cell: cell)
// }
//
// private func didSelectItem(atIndexPath indexPath: IndexPath,
// cell: UICollectionViewCell?) {
//
// guard let item = item(at: indexPath) else { return }
//
// delegate?.didSelectItem(atIndexPath: indexPath)
//
// item.didSelectItem(atIndexPath: indexPath, cell: cell)
// }
//
// private func bind(to collectionView: UICollectionView) {
// self.collectionView = collectionView
//
// collectionView.delegate = self
// collectionView.dataSource = self
// }
//
// private func item(at indexPath: IndexPath) -> FilterItem? {
// let index = indexPath.item
// let amount = collectionItems.count
//
// return (index < amount && index >= 0) ? collectionItems[index] : nil
// }
//}

View File

@ -0,0 +1,81 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
open class DefaultFiltersViewModel: NSObject,
FiltersViewModelProtocol,
UICollectionViewDelegate,
UICollectionViewDataSource {
public typealias Change = (indexPath: IndexPath, viewModel: FilterCellViewModelProtocol)
public var filters: [DefaultFilterPropertyValue]
public var selectedFilters: Set<DefaultFilterPropertyValue> = []
public var cellsViewModels: [FilterCellViewModelProtocol]
public weak var filtersCollectionHolder: FiltersCollectionHolder?
public init(filters: [DefaultFilterPropertyValue]) {
self.filters = filters
self.cellsViewModels = filters.compactMap { $0.convertToViewModel() as? FilterCellViewModelProtocol }
}
// MARK: - UICollectionViewDataSource
open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
filtersCollectionHolder?.registerCells()
return cellsViewModels.count
}
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let viewModel = cellsViewModels[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.id, for: indexPath)
filtersCollectionHolder?.configure(filterCell: cell, cellViewModel: viewModel)
return cell
}
// MARK: - UICollectionViewDelegate
open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let (selected, deselected) = toggleFilter(atIndexPath: indexPath)
let changedFilters = filters
.enumerated()
.filter { selected.contains($0.element) || deselected.contains($0.element) }
for (offset, element) in changedFilters {
cellsViewModels[offset].isSelected = selectedFilters.contains(element)
filters[offset].isSelected = selectedFilters.contains(element)
}
let changedItems = changedFilters
.map {
Change(indexPath: IndexPath(item: $0.offset, section: .zero),
viewModel: cellsViewModels[$0.offset])
}
filtersCollectionHolder?.applyChange(changedItems)
}
}

View File

@ -23,14 +23,15 @@
import UIKit
import TIUIKitCore
open class BaseFilterCollectionCell: UICollectionViewCell,
open class BaseFilterCollectionCell: ContainerCollectionViewCell<UILabel>,
SelectableCell,
InitializableViewProtocol,
ConfigurableView {
public let titleLabel = UILabel()
public override var wrappedView: UILabel {
UILabel()
}
public var viewModel: DefaultCellViewModel?
public var viewModel: DefaultFilterCellViewModel?
open var isFilterSelected: Bool = false {
didSet {
@ -40,68 +41,28 @@ open class BaseFilterCollectionCell: UICollectionViewCell,
open var isLabelHidden: Bool {
get {
titleLabel.isHidden
wrappedView.isHidden
}
set {
titleLabel.isHidden = newValue
wrappedView.isHidden = newValue
}
}
open override var intrinsicContentSize: CGSize {
let contentSize = super.intrinsicContentSize
let insets = viewModel?.insets ?? .zero
let xInsets = insets.left + insets.right
let yInsets = insets.top + insets.bottom
return .init(width: contentSize.width + xInsets, height: contentSize.height + yInsets)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
addViews()
configureLayout()
bindViews()
configureAppearance()
localize()
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
open func addViews() {
addSubview(titleLabel)
}
open func configureLayout() {
titleLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(titleLabel.edgesEqualToSuperview())
}
open func bindViews() {
// override in subclass
}
open func configureAppearance() {
open override func configureAppearance() {
layer.round(corners: .allCorners, radius: 6)
}
open func localize() {
// override in subclass
}
open func configure(with viewModel: DefaultCellViewModel) {
open func configure(with viewModel: DefaultFilterCellViewModel) {
self.viewModel = viewModel
titleLabel.text = viewModel.title
wrappedView.text = viewModel.title
setSelected(isSelected: viewModel.isSelected)
}
open func setSelected(isSelected: Bool) {
let selectedColor = viewModel?.selectedColor ?? .green
titleLabel.textColor = isSelected ? selectedColor : .black
wrappedView.textColor = isSelected ? selectedColor : .black
if isSelected {
backgroundColor = .white

View File

@ -23,33 +23,33 @@
import UIKit
import TIUIKitCore
public typealias AnyCollectionCell = SelectableCell & ConfigurableView
//public typealias AnyCollectionCell = SelectableCell & ConfigurableView
open class BaseFilterCollectionItem<CellType: AnyCollectionCell,
Filter: FilterPropertyValueRepresenter>: FilterCollectionItem where CellType.ViewModelType: CellViewModelRepresentable {
public var itemType: AnyClass{
CellType.self
}
public var filter: Filter
public var viewModel: CellViewModelRepresentable?
required public init(filter: Filter, viewModel: CellViewModelRepresentable) {
self.filter = filter
self.viewModel = viewModel
}
open func configure(item: UICollectionViewCell) {
guard let viewModel = viewModel as? CellType.ViewModelType else {
return
}
(item as? CellType)?.configure(with: viewModel)
}
open func didSelectItem(atIndexPath indexPath: IndexPath, cell: UICollectionViewCell?) {
// Override in subviews
}
}
//open class BaseFilterCollectionItem<CellType: AnyCollectionCell,
// Filter: FilterPropertyValueRepresenter>: FilterCollectionItem where CellType.ViewModelType: CellViewModelRepresentable {
//
// public var itemType: AnyClass{
// CellType.self
// }
//
// public var filter: Filter
//
// public var viewModel: CellViewModelRepresentable?
//
// required public init(filter: Filter, viewModel: CellViewModelRepresentable) {
// self.filter = filter
// self.viewModel = viewModel
// }
//
// open func configure(item: UICollectionViewCell) {
// guard let viewModel = viewModel as? CellType.ViewModelType else {
// return
// }
//
// (item as? CellType)?.configure(with: viewModel)
// }
//
// open func didSelectItem(atIndexPath indexPath: IndexPath, cell: UICollectionViewCell?) {
// // Override in subviews
// }
//}

View File

@ -23,33 +23,25 @@
import TIUIKitCore
import UIKit
public typealias DefaultBaseFiltersCollectionView = BaseFiltersCollectionView<BaseFilterCollectionItem<BaseFilterCollectionCell,
DefaultFilterPropertyValue>>
open class BaseFiltersCollectionView<CellType: UICollectionViewCell & ConfigurableView>: UICollectionView,
InitializableViewProtocol,
ConfigurableView,
FiltersCollectionHolder where CellType.ViewModelType: FilterCellViewModelProtocol {
open class BaseFiltersCollectionView<Cell: FilterCollectionItem>: BaseInitializableView,
ConfigurableView,
FiltersCollectionHolder {
public typealias Director = DefaultFiltersCollectionDirector<Cell>
public var collectionDirector: Director
public var collectionView: UICollectionView
public var layout: CollectionViewLayoutProtocol
public var layout: UICollectionViewLayout
public weak var viewModel: DefaultFiltersViewModel?
open weak var filtersDelegate: FilterItemsDelegate?
open var collectionView: UICollectionView {
self
}
// MARK: - Init
public init(layout: CollectionViewLayoutProtocol) {
public init(layout: UICollectionViewLayout) {
self.layout = layout
self.collectionView = .init(frame: .zero, collectionViewLayout: layout.collectionViewLayout)
self.collectionDirector = .init(collectionView: collectionView)
super.init(frame: .zero)
super.init(frame: .zero, collectionViewLayout: layout)
}
required public init?(coder aDecoder: NSCoder) {
@ -58,70 +50,66 @@ open class BaseFiltersCollectionView<Cell: FilterCollectionItem>: BaseInitializa
// MARK: - Life cycle
open override func addViews() {
super.addViews()
open func addViews() {
addSubview(collectionView)
}
open override func configureLayout() {
super.configureLayout()
open func configureLayout() {
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(collectionView.edgesEqualToSuperview())
}
open override func configureAppearance() {
super.configureAppearance()
open func bindViews() {
delegate = viewModel
dataSource = viewModel
}
open func configureAppearance() {
backgroundColor = .white
collectionView.backgroundColor = .white
}
open func localize() {
// override in subclass
}
// MARK: - ConfigurableView
open func configure(with viewModel: DefaultFiltersViewModel) {
self.viewModel = viewModel
viewModel.filtersCollectionHolder = self
collectionDirector.delegate = viewModel
updateView()
}
// MARK: - FiltersCollectionHolder
open func select(_ items: [FilterPropertyValueRepresenter]) {
guard let viewModel = viewModel else { return }
items.forEach { item in
if let index = viewModel.filters.firstIndex(where: { $0.id == item.id }) {
viewModel.filters[index].isSelected = true
open func applyChange(_ changes: [DefaultFiltersViewModel.Change]) {
for change in changes {
guard let cell = cellForItem(at: change.indexPath) else {
continue
}
}
}
open func deselect(_ items: [FilterPropertyValueRepresenter]) {
guard let viewModel = viewModel else { return }
items.forEach { item in
if let index = viewModel.filters.firstIndex(where: { $0.id == item.id }) {
viewModel.filters[index].isSelected = false
}
configure(filterCell: cell, cellViewModel: change.viewModel)
}
}
open func updateView() {
guard let viewModel = viewModel else { return }
reloadData()
}
collectionDirector.collectionItems = viewModel.filters.compactMap {
guard let filter = $0 as? Cell.Filter else {
return nil
}
open func configure(filterCell: UICollectionViewCell, cellViewModel: FilterCellViewModelProtocol) {
guard let cellViewModel = cellViewModel as? CellType.ViewModelType else { return }
return Cell(filter: filter, viewModel: $0.convertToViewModel())
}
guard let configurableCell = filterCell as? CellType else { return }
configurableCell.configure(with: cellViewModel)
}
open func registerCells() {
viewModel?.filters.forEach { self.register(CellType.self, forCellWithReuseIdentifier: $0.id) }
}
}

View File

@ -1,27 +0,0 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public protocol CollectionViewLayoutProtocol {
var collectionViewLayout: UICollectionViewLayout { get }
}

View File

@ -1,128 +0,0 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
open class DefaultFiltersCollectionDirector<FilterItem: FilterCollectionItem>: NSObject,
CollectionDirectorRepresenter,
UICollectionViewDataSource,
UICollectionViewDelegate {
private var _collectionItems: [FilterItem] = []
public weak var collectionView: UICollectionView?
public weak var delegate: FilterItemsDelegate?
public var collectionItems: [FilterItem] {
get {
_collectionItems
}
set {
_collectionItems = newValue
collectionView?.reloadData()
}
}
public init(collectionView: UICollectionView) {
super.init()
bind(to: collectionView)
}
public func insertItem(_ item: FilterItem, at index: Int) {
collectionItems.insert(item, at: index)
let indexPath = IndexPath(row: index, section: .zero)
guard collectionItems.count != 1 else {
return
}
scrollToItem(at: indexPath)
}
public func deleteItem(at index: Int) {
collectionItems.remove(at: index)
let indexPath = IndexPath(row: max(index - 1, .zero), section: .zero)
scrollToItem(at: indexPath)
}
public func update(item: FilterItem, at index: Int) {
_collectionItems[index] = item
}
public func scrollToItem(at indexPath: IndexPath, animated: Bool = true) {
collectionView?.performBatchUpdates(nil, completion: { [weak self] isReady in
if isReady {
self?.collectionView?.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: animated)
}
})
}
// MARK: - UICollectionViewDataSource
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
collectionItems.forEach { $0.register(for: collectionView) }
return collectionItems.count
}
public func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let viewModel = collectionItems[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.identifier, for: indexPath)
viewModel.configure(item: cell)
return cell
}
// MARK: - UICollectionViewDelegate
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
didSelectItem(atIndexPath: indexPath, cell: cell)
}
private func didSelectItem(atIndexPath indexPath: IndexPath,
cell: UICollectionViewCell?) {
guard let item = item(at: indexPath) else { return }
delegate?.didSelectItem(atIndexPath: indexPath)
item.didSelectItem(atIndexPath: indexPath, cell: cell)
}
private func bind(to collectionView: UICollectionView) {
self.collectionView = collectionView
collectionView.delegate = self
collectionView.dataSource = self
}
private func item(at indexPath: IndexPath) -> FilterItem? {
let index = indexPath.item
let amount = collectionItems.count
return (index < amount && index >= 0) ? collectionItems[index] : nil
}
}

View File

@ -1,39 +0,0 @@
//
// Copyright (c) 2022 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
open class DefaultFiltersViewModel: FiltersViewModelProtocol, FilterItemsDelegate {
public var filters: [DefaultFilterPropertyValue]
public var selectedFilters: Set<DefaultFilterPropertyValue> = []
public weak var filtersCollectionHolder: FiltersCollectionHolder?
public init(filters: [DefaultFilterPropertyValue]) {
self.filters = filters
}
open func didSelectItem(atIndexPath indexPath: IndexPath) {
filterItem(atIndexPath: indexPath)
}
}