From ae52ec03c67ebcd3d0c3225e79974fc2183fdd9b Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Thu, 4 Aug 2022 21:34:14 +0300 Subject: [PATCH 01/21] fix: edge insets init + refactoring --- .../UICollectionViewLayout+DefaultLayout.swift | 0 .../Models/BaseFilterCellAppearance.swift | 0 .../Models/FiltersLayoutConfiguration.swift | 0 .../Protocols/FilterCellAppearanceProtocol.swift | 0 .../Protocols}/FilterCellViewModelProtocol.swift | 0 .../Protocols/FilterPropertyValueRepresenter.swift | 0 .../Protocols/FilterRepresenter.swift | 0 .../Protocols/FiltersViewModelProtocol.swift | 0 .../ViewModels/DefaultFilterCellViewModel.swift | 0 .../ViewModels/DefaultFiltersViewModel.swift | 0 .../Views/BaseFiltersCollectionView.swift | 0 .../Views/DefaultFilterCollectionCell.swift | 0 TIUIElements/Sources/Wrappers/EdgeConstraints.swift | 11 +++++++++++ 13 files changed, 11 insertions(+) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/Helpers/UICollectionViewLayout+DefaultLayout.swift (100%) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/Models/BaseFilterCellAppearance.swift (100%) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/Models/FiltersLayoutConfiguration.swift (100%) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/Protocols/FilterCellAppearanceProtocol.swift (100%) rename TIEcommerce/Sources/Filters/{Models => TagsFilters/Protocols}/FilterCellViewModelProtocol.swift (100%) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/Protocols/FilterPropertyValueRepresenter.swift (100%) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/Protocols/FilterRepresenter.swift (100%) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/Protocols/FiltersViewModelProtocol.swift (100%) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/ViewModels/DefaultFilterCellViewModel.swift (100%) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/ViewModels/DefaultFiltersViewModel.swift (100%) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/Views/BaseFiltersCollectionView.swift (100%) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/Views/DefaultFilterCollectionCell.swift (100%) diff --git a/TIEcommerce/Sources/Filters/Helpers/UICollectionViewLayout+DefaultLayout.swift b/TIEcommerce/Sources/Filters/TagsFilters/Helpers/UICollectionViewLayout+DefaultLayout.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Helpers/UICollectionViewLayout+DefaultLayout.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Helpers/UICollectionViewLayout+DefaultLayout.swift diff --git a/TIEcommerce/Sources/Filters/Models/BaseFilterCellAppearance.swift b/TIEcommerce/Sources/Filters/TagsFilters/Models/BaseFilterCellAppearance.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Models/BaseFilterCellAppearance.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Models/BaseFilterCellAppearance.swift diff --git a/TIEcommerce/Sources/Filters/Models/FiltersLayoutConfiguration.swift b/TIEcommerce/Sources/Filters/TagsFilters/Models/FiltersLayoutConfiguration.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Models/FiltersLayoutConfiguration.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Models/FiltersLayoutConfiguration.swift diff --git a/TIEcommerce/Sources/Filters/Protocols/FilterCellAppearanceProtocol.swift b/TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterCellAppearanceProtocol.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Protocols/FilterCellAppearanceProtocol.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterCellAppearanceProtocol.swift diff --git a/TIEcommerce/Sources/Filters/Models/FilterCellViewModelProtocol.swift b/TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterCellViewModelProtocol.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Models/FilterCellViewModelProtocol.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterCellViewModelProtocol.swift diff --git a/TIEcommerce/Sources/Filters/Protocols/FilterPropertyValueRepresenter.swift b/TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterPropertyValueRepresenter.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Protocols/FilterPropertyValueRepresenter.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterPropertyValueRepresenter.swift diff --git a/TIEcommerce/Sources/Filters/Protocols/FilterRepresenter.swift b/TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterRepresenter.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Protocols/FilterRepresenter.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Protocols/FilterRepresenter.swift diff --git a/TIEcommerce/Sources/Filters/Protocols/FiltersViewModelProtocol.swift b/TIEcommerce/Sources/Filters/TagsFilters/Protocols/FiltersViewModelProtocol.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Protocols/FiltersViewModelProtocol.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Protocols/FiltersViewModelProtocol.swift diff --git a/TIEcommerce/Sources/Filters/ViewModels/DefaultFilterCellViewModel.swift b/TIEcommerce/Sources/Filters/TagsFilters/ViewModels/DefaultFilterCellViewModel.swift similarity index 100% rename from TIEcommerce/Sources/Filters/ViewModels/DefaultFilterCellViewModel.swift rename to TIEcommerce/Sources/Filters/TagsFilters/ViewModels/DefaultFilterCellViewModel.swift diff --git a/TIEcommerce/Sources/Filters/ViewModels/DefaultFiltersViewModel.swift b/TIEcommerce/Sources/Filters/TagsFilters/ViewModels/DefaultFiltersViewModel.swift similarity index 100% rename from TIEcommerce/Sources/Filters/ViewModels/DefaultFiltersViewModel.swift rename to TIEcommerce/Sources/Filters/TagsFilters/ViewModels/DefaultFiltersViewModel.swift diff --git a/TIEcommerce/Sources/Filters/Views/BaseFiltersCollectionView.swift b/TIEcommerce/Sources/Filters/TagsFilters/Views/BaseFiltersCollectionView.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Views/BaseFiltersCollectionView.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Views/BaseFiltersCollectionView.swift diff --git a/TIEcommerce/Sources/Filters/Views/DefaultFilterCollectionCell.swift b/TIEcommerce/Sources/Filters/TagsFilters/Views/DefaultFilterCollectionCell.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Views/DefaultFilterCollectionCell.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Views/DefaultFilterCollectionCell.swift diff --git a/TIUIElements/Sources/Wrappers/EdgeConstraints.swift b/TIUIElements/Sources/Wrappers/EdgeConstraints.swift index 0274afd8..8afbc0ca 100644 --- a/TIUIElements/Sources/Wrappers/EdgeConstraints.swift +++ b/TIUIElements/Sources/Wrappers/EdgeConstraints.swift @@ -28,6 +28,17 @@ public struct EdgeConstraints { public let topConstraint: NSLayoutConstraint public let bottomConstraint: NSLayoutConstraint + public init(leadingConstraint: NSLayoutConstraint, + trailingConstraint: NSLayoutConstraint, + topConstraint: NSLayoutConstraint, + bottomConstraint: NSLayoutConstraint) { + + self.leadingConstraint = leadingConstraint + self.trailingConstraint = trailingConstraint + self.topConstraint = topConstraint + self.bottomConstraint = bottomConstraint + } + public var allConstraints: [NSLayoutConstraint] { [ leadingConstraint, From 4c525b8dad9da5a03bd66630330f08e90e636e11 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Thu, 4 Aug 2022 22:45:06 +0300 Subject: [PATCH 02/21] feat: list of filters --- .../Models/DefaultFilterRowViewModel.swift | 32 +++++ .../FilterListPickerConfigurator.swift | 32 +++++ .../Protocols/FilterRowRepresentable.swift | 28 +++++ .../Protocols/FilterTableSectionBuilder.swift | 30 +++++ .../Protocols/FiltersPickerDelegate.swift | 25 ++++ .../ViewModels/BaseListFilterViewModel.swift | 93 +++++++++++++++ .../DefaultFilterListSectionBuilder.swift | 49 ++++++++ .../Views/BaseCustomTableView.swift | 76 ++++++++++++ .../Views/BaseListFilterPickerView.swift | 65 +++++++++++ .../Views/DefaultFilterListCell.swift | 110 ++++++++++++++++++ .../Models/DefaultFilterModel.swift | 0 .../Models/DefaultFilterPropertyValue.swift | 0 TIEcommerce/TIEcommerce.podspec | 4 +- 13 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift create mode 100644 TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift create mode 100644 TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterRowRepresentable.swift create mode 100644 TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift create mode 100644 TIEcommerce/Sources/Filters/ListFilters/Protocols/FiltersPickerDelegate.swift create mode 100644 TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift create mode 100644 TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift create mode 100644 TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift create mode 100644 TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift create mode 100644 TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift rename TIEcommerce/Sources/Filters/{ => TagsFilters}/Models/DefaultFilterModel.swift (100%) rename TIEcommerce/Sources/Filters/{ => TagsFilters}/Models/DefaultFilterPropertyValue.swift (100%) diff --git a/TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift b/TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift new file mode 100644 index 00000000..f33effe5 --- /dev/null +++ b/TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift @@ -0,0 +1,32 @@ +// +// 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. +// + +public struct DefaultFilterRowViewModel: FilterRowRepresentable, Equatable { + public let id: String + public let title: String + public var appearance: FilterCellAppearanceProtocol + public var isSelected: Bool + + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.id == rhs.id + } +} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift new file mode 100644 index 00000000..1ea0aa23 --- /dev/null +++ b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift @@ -0,0 +1,32 @@ +// +// 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. +// + +public protocol FilterListPickerConfigurator: AnyObject { + + associatedtype CellViewModel: FilterRowRepresentable & Equatable + + var visibleValues: [CellViewModel] { get } + var isMultiselectionEnabled: Bool { get set } + var isFinishWithSeparator: Bool { get } + + func setSelected(model: CellViewModel, isSelected: Bool) +} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterRowRepresentable.swift b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterRowRepresentable.swift new file mode 100644 index 00000000..e908c62b --- /dev/null +++ b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterRowRepresentable.swift @@ -0,0 +1,28 @@ +// +// 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. +// + +public protocol FilterRowRepresentable { + var id: String { get } + var title: String { get } + var isSelected: Bool { get set } + var appearance: FilterCellAppearanceProtocol { get set } +} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift new file mode 100644 index 00000000..9146ad18 --- /dev/null +++ b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift @@ -0,0 +1,30 @@ +// +// 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 TableKit +import TIUIElements + +public protocol FilterTableSectionBuilder { + associatedtype CellType: BaseSeparatorCell & ConfigurableCell + + func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.CellViewModel == CellType.CellData +} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FiltersPickerDelegate.swift b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FiltersPickerDelegate.swift new file mode 100644 index 00000000..a7008e31 --- /dev/null +++ b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FiltersPickerDelegate.swift @@ -0,0 +1,25 @@ +// +// 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. +// + +public protocol FiltersPickerDelegate: AnyObject { + func filters(didSelect filters: [FilterRowRepresentable]) +} diff --git a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift new file mode 100644 index 00000000..78624d54 --- /dev/null +++ b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift @@ -0,0 +1,93 @@ +// +// 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. +// + +open class BaseListFilterViewModel: FilterListPickerConfigurator { + + public typealias CellViewModel = CellViewModelType + + public var rowViewModels: [CellViewModelType] + + public weak var delegate: FiltersPickerDelegate? + + public var initiallySelectedValues: [CellViewModelType]? + + open var isFinishWithSeparator: Bool { + true + } + + open var isMultiselectionEnabled: Bool = false + + open var areInitiallySelectedValuesTheSame: Bool { + guard let initiallySelectedValues = initiallySelectedValues else { + return false + } + + return initiallySelectedValues == selectedValues + } + + open var areThereAnyValuesSelected: Bool { + if let _ = rowViewModels.first(where: { $0.isSelected }) { + return false + } + + return true + } + + open var selectedValues: [CellViewModelType] { + rowViewModels.filter { $0.isSelected } + } + + open var visibleValues: [CellViewModelType] { + rowViewModels + } + + open var visibleSelectedIndexes: [Int] { + visibleValues.enumerated().compactMap { + $0.element.isSelected ? $0.offset : nil + } + } + + public init(rowViewModels: [CellViewModelType], isMultiselectionEnabled: Bool) { + self.rowViewModels = rowViewModels + self.isMultiselectionEnabled = isMultiselectionEnabled + } + + open func viewDidLoad() { + initiallySelectedValues = selectedValues + } + + open func setSelected(model: CellViewModelType, isSelected: Bool) { + if !isMultiselectionEnabled { + rowViewModels.enumerated().forEach { + rowViewModels[$0.offset].isSelected = false + } + } + + if let index = rowViewModels.firstIndex(of: model) { + rowViewModels[index].isSelected = isSelected + } + + if !selectedValues.isEmpty { + delegate?.filters(didSelect: selectedValues) + } + } +} diff --git a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift new file mode 100644 index 00000000..624f5e84 --- /dev/null +++ b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift @@ -0,0 +1,49 @@ +// +// 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 TableKit +import TITableKitUtils +import TIUIElements + +open class DefaultFilterListSectionBuilder: FilterTableSectionBuilder { + + open func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.CellViewModel == CellType.CellData { + let rows = viewModel.visibleValues.map { item in + TableRow(item: item) + .on(.select) { [weak viewModel] _ in + viewModel?.setSelected(model: item, isSelected: !item.isSelected) + } + .on(.deselect) { [weak viewModel] _ in + viewModel?.setSelected(model: item, isSelected: false) + } + } + + let separatedRows: [SeparatorRowBox] = rows.map { $0.separatorRowBox } + let separator = SeparatorConfiguration(color: .gray) + + separatedRows.configureSeparators(first: separator, + middle: separator, + last: viewModel.isFinishWithSeparator ? separator : SeparatorConfiguration(color: .clear)) + + return .init(rows: separatedRows.rows) + } +} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift new file mode 100644 index 00000000..5f133658 --- /dev/null +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift @@ -0,0 +1,76 @@ +// +// 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 TableKit +import TIUIElements +import TIUIKitCore +import UIKit + +open class BaseCustomTableView: BaseInitializableView, UIScrollViewDelegate { + + private var tableViewContraints: EdgeConstraints? + + private lazy var tableDirector = TableDirector(tableView: tableView, scrollDelegate: self) + + public let tableView = UITableView(frame: .zero, style: .grouped) + + public weak var scrollableViewDelegate: UIScrollViewDelegate? + + open override func addViews() { + super.addViews() + + addSubview(tableView) + } + + open override func configureLayout() { + super.configureLayout() + + tableView.translatesAutoresizingMaskIntoConstraints = false + + tableViewContraints = .init(leadingConstraint: tableView.leadingAnchor.constraint(equalTo: leadingAnchor), + trailingConstraint: tableView.trailingAnchor.constraint(equalTo: trailingAnchor), + topConstraint: tableView.topAnchor.constraint(equalTo: topAnchor), + bottomConstraint: tableView.topAnchor.constraint(equalTo: topAnchor)) + + tableViewContraints?.activate() + } + + open override func configureAppearance() { + super.configureAppearance() + + tableView.separatorStyle = .none + + [self, tableView].forEach { $0.backgroundColor = .white } + } + + open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + scrollableViewDelegate?.scrollViewWillBeginDragging?(scrollView) + } + + open func scrollViewDidScroll(_ scrollView: UIScrollView) { + scrollableViewDelegate?.scrollViewDidScroll?(scrollView) + } + + open func updateTableView(_ section: TableSection) { + tableDirector.replace(withSection: section) + } +} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift new file mode 100644 index 00000000..fcafc5f7 --- /dev/null +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift @@ -0,0 +1,65 @@ +// +// 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 TableKit +import TIUIElements +import UIKit + +open class BaseListFilterPickerView: BaseCustomTableView where CellType.CellData: FilterRowRepresentable & Equatable { + + public let builder = DefaultFilterListSectionBuilder() + public weak var viewModel: BaseListFilterViewModel? + + public init(viewModel: BaseListFilterViewModel) { + self.viewModel = viewModel + + super.init(frame: .zero) + + tableView.allowsMultipleSelection = viewModel.isMultiselectionEnabled + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func configureAppearance() { + super.configureAppearance() + + tableView.alwaysBounceVertical = false + viewModel?.viewDidLoad() + + reloadData(with: viewModel?.visibleSelectedIndexes ?? []) + } + + open func reloadData(with selectedIndexes: [Int]) { + guard let viewModel = viewModel else { return } + + let elements = builder.makeSection(with: viewModel) + updateTableView(elements) + + selectedIndexes.forEach { + tableView.selectRow(at: IndexPath(row: $0, section: .zero), + animated: false, + scrollPosition: .none) + } + } +} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift new file mode 100644 index 00000000..ef149287 --- /dev/null +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift @@ -0,0 +1,110 @@ +// +// 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 TableKit +import TIUIElements +import UIKit + +open class DefaultFilterListCell: BaseSeparatorCell, ConfigurableCell { + + private var titleLeadingConstraint: NSLayoutConstraint! + private var titleTopConstraint: NSLayoutConstraint! + private var titleBottomConstraint: NSLayoutConstraint! + + private var imageTrailingConstraint: NSLayoutConstraint! + private var imageTopConstraint: NSLayoutConstraint! + private var imageBottomConstraint: NSLayoutConstraint! + + public lazy var titleLabel = UILabel() + public lazy var selectionCheckmarkImageView = UIImageView() + + open var selectedImage: UIImage? { + if #available(iOS 13.0, *) { + return UIImage(systemName: "checkmark")! + } + + return nil + } + + open var deselectedImage: UIImage? { + nil + } + + open override func addViews() { + super.addViews() + + contentView.addSubviews(titleLabel, selectionCheckmarkImageView) + } + + open override func configureLayout() { + super.configureLayout() + + titleLabel.translatesAutoresizingMaskIntoConstraints = false + selectionCheckmarkImageView.translatesAutoresizingMaskIntoConstraints = false + + titleLeadingConstraint = titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor) + titleTopConstraint = titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor) + titleBottomConstraint = titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + + imageTrailingConstraint = selectionCheckmarkImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) + imageTopConstraint = selectionCheckmarkImageView.topAnchor.constraint(equalTo: contentView.topAnchor) + imageBottomConstraint = selectionCheckmarkImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + + + NSLayoutConstraint.activate([ + titleLeadingConstraint, + titleTopConstraint, + titleBottomConstraint, + imageTrailingConstraint, + imageTopConstraint, + imageBottomConstraint + ]) + } + + open func configure(with viewModel: DefaultFilterRowViewModel) { + titleLabel.text = viewModel.title + + setContentInsets(viewModel.appearance.contentInsets) + contentView.backgroundColor = viewModel.appearance.deselectedBgColor + } + + open func setContentInsets(_ insets: UIEdgeInsets) { + titleLeadingConstraint.constant = insets.left + imageTrailingConstraint.constant = -insets.right + + titleTopConstraint.constant = insets.top + imageTopConstraint.constant = insets.top + + titleBottomConstraint.constant = -insets.bottom + imageBottomConstraint.constant = -insets.bottom + } + + open override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + if selected { + selectionCheckmarkImageView.image = selectedImage + } else { + selectionCheckmarkImageView.image = deselectedImage + } + } +} diff --git a/TIEcommerce/Sources/Filters/Models/DefaultFilterModel.swift b/TIEcommerce/Sources/Filters/TagsFilters/Models/DefaultFilterModel.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Models/DefaultFilterModel.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Models/DefaultFilterModel.swift diff --git a/TIEcommerce/Sources/Filters/Models/DefaultFilterPropertyValue.swift b/TIEcommerce/Sources/Filters/TagsFilters/Models/DefaultFilterPropertyValue.swift similarity index 100% rename from TIEcommerce/Sources/Filters/Models/DefaultFilterPropertyValue.swift rename to TIEcommerce/Sources/Filters/TagsFilters/Models/DefaultFilterPropertyValue.swift diff --git a/TIEcommerce/TIEcommerce.podspec b/TIEcommerce/TIEcommerce.podspec index 481f80b3..e09cdef1 100644 --- a/TIEcommerce/TIEcommerce.podspec +++ b/TIEcommerce/TIEcommerce.podspec @@ -14,6 +14,8 @@ Pod::Spec.new do |s| s.dependency 'TIFoundationUtils', s.version.to_s s.dependency 'TINetworking', s.version.to_s - s.dependency 'TIUIKitCore', s.version.to_s + s.dependency 'TITableKitUtils', version.to_s s.dependency 'TIUIElements', version.to_s + s.dependency 'TIUIKitCore', s.version.to_s + s.dependency 'TableKit', '~> 2.11' end From 6ef2c8399013e822e75cc5c05efb5f394ba18c1b Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Thu, 4 Aug 2022 22:49:47 +0300 Subject: [PATCH 03/21] fix: access to view model initializer + renames --- .../Models/DefaultFilterRowViewModel.swift | 11 +++++++++++ .../Protocols/FilterListPickerConfigurator.swift | 6 +++--- .../Protocols/FilterRowRepresentable.swift | 5 +++++ .../Protocols/FilterTableSectionBuilder.swift | 2 +- .../ViewModels/BaseListFilterViewModel.swift | 16 ++++++++-------- .../DefaultFilterListSectionBuilder.swift | 2 +- .../Protocols/FiltersViewModelProtocol.swift | 4 ++-- .../ViewModels/DefaultFiltersViewModel.swift | 10 +++++----- 8 files changed, 36 insertions(+), 20 deletions(-) diff --git a/TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift b/TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift index f33effe5..0e2e0643 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift @@ -26,6 +26,17 @@ public struct DefaultFilterRowViewModel: FilterRowRepresentable, Equatable { public var appearance: FilterCellAppearanceProtocol public var isSelected: Bool + public init(id: String, + title: String, + appearance: FilterCellAppearanceProtocol, + isSelected: Bool) { + + self.id = id + self.title = title + self.appearance = appearance + self.isSelected = isSelected + } + public static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id } diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift index 1ea0aa23..55b6f91e 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift @@ -22,11 +22,11 @@ public protocol FilterListPickerConfigurator: AnyObject { - associatedtype CellViewModel: FilterRowRepresentable & Equatable + associatedtype RowViewModel: FilterRowRepresentable & Equatable - var visibleValues: [CellViewModel] { get } + var visibleValues: [RowViewModel] { get } var isMultiselectionEnabled: Bool { get set } var isFinishWithSeparator: Bool { get } - func setSelected(model: CellViewModel, isSelected: Bool) + func setSelected(model: RowViewModel, isSelected: Bool) } diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterRowRepresentable.swift b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterRowRepresentable.swift index e908c62b..74467f5f 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterRowRepresentable.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterRowRepresentable.swift @@ -25,4 +25,9 @@ public protocol FilterRowRepresentable { var title: String { get } var isSelected: Bool { get set } var appearance: FilterCellAppearanceProtocol { get set } + + init(id: String, + title: String, + appearance: FilterCellAppearanceProtocol, + isSelected: Bool) } diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift index 9146ad18..998e735a 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift @@ -26,5 +26,5 @@ import TIUIElements public protocol FilterTableSectionBuilder { associatedtype CellType: BaseSeparatorCell & ConfigurableCell - func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.CellViewModel == CellType.CellData + func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.RowViewModel == CellType.CellData } diff --git a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift index 78624d54..4df0a8cd 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift @@ -20,15 +20,15 @@ // THE SOFTWARE. // -open class BaseListFilterViewModel: FilterListPickerConfigurator { +open class BaseListFilterViewModel: FilterListPickerConfigurator { - public typealias CellViewModel = CellViewModelType + public typealias RowViewModel = RowViewModelType - public var rowViewModels: [CellViewModelType] + public var rowViewModels: [RowViewModelType] public weak var delegate: FiltersPickerDelegate? - public var initiallySelectedValues: [CellViewModelType]? + public var initiallySelectedValues: [RowViewModelType]? open var isFinishWithSeparator: Bool { true @@ -52,11 +52,11 @@ open class BaseListFilterViewModel: FilterTableSectionBuilder { - open func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.CellViewModel == CellType.CellData { + open func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.RowViewModel == CellType.CellData { let rows = viewModel.visibleValues.map { item in TableRow(item: item) .on(.select) { [weak viewModel] _ in diff --git a/TIEcommerce/Sources/Filters/TagsFilters/Protocols/FiltersViewModelProtocol.swift b/TIEcommerce/Sources/Filters/TagsFilters/Protocols/FiltersViewModelProtocol.swift index d9f7c28a..a0d9b05d 100644 --- a/TIEcommerce/Sources/Filters/TagsFilters/Protocols/FiltersViewModelProtocol.swift +++ b/TIEcommerce/Sources/Filters/TagsFilters/Protocols/FiltersViewModelProtocol.swift @@ -25,9 +25,9 @@ import Foundation public protocol FiltersViewModelProtocol: AnyObject { associatedtype Filter: FilterPropertyValueRepresenter, Hashable - associatedtype CellViewModel: FilterCellViewModelProtocol & Hashable + associatedtype RowViewModel: FilterCellViewModelProtocol & Hashable - typealias Change = (indexPath: IndexPath, viewModel: CellViewModel) + typealias Change = (indexPath: IndexPath, viewModel: RowViewModel) var filters: [Filter] { get set } var selectedFilters: Set { get set } diff --git a/TIEcommerce/Sources/Filters/TagsFilters/ViewModels/DefaultFiltersViewModel.swift b/TIEcommerce/Sources/Filters/TagsFilters/ViewModels/DefaultFiltersViewModel.swift index eb2fb2f9..4e214187 100644 --- a/TIEcommerce/Sources/Filters/TagsFilters/ViewModels/DefaultFiltersViewModel.swift +++ b/TIEcommerce/Sources/Filters/TagsFilters/ViewModels/DefaultFiltersViewModel.swift @@ -23,12 +23,12 @@ import TIUIKitCore import UIKit -open class DefaultFiltersViewModel: NSObject, +open class DefaultFiltersViewModel: NSObject, FiltersViewModelProtocol { - public typealias CellViewModel = CellViewModelType + public typealias RowViewModel = RowViewModelType - private var cellsViewModels: [CellViewModelType] + private var cellsViewModels: [RowViewModelType] public var filters: [DefaultFilterPropertyValue] { didSet { @@ -50,7 +50,7 @@ open class DefaultFiltersViewModel Date: Thu, 4 Aug 2022 22:57:55 +0300 Subject: [PATCH 04/21] fix: base table view constraints --- .../ListFilters/Views/BaseCustomTableView.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift index 5f133658..a5fd4672 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift @@ -27,7 +27,8 @@ import UIKit open class BaseCustomTableView: BaseInitializableView, UIScrollViewDelegate { - private var tableViewContraints: EdgeConstraints? + private var tableViewEdgeContraints: EdgeConstraints! + private var tableViewHeightConstraint: NSLayoutConstraint! private lazy var tableDirector = TableDirector(tableView: tableView, scrollDelegate: self) @@ -46,12 +47,17 @@ open class BaseCustomTableView: BaseInitializableView, UIScrollViewDelegate { tableView.translatesAutoresizingMaskIntoConstraints = false - tableViewContraints = .init(leadingConstraint: tableView.leadingAnchor.constraint(equalTo: leadingAnchor), + tableViewEdgeContraints = .init(leadingConstraint: tableView.leadingAnchor.constraint(equalTo: leadingAnchor), trailingConstraint: tableView.trailingAnchor.constraint(equalTo: trailingAnchor), topConstraint: tableView.topAnchor.constraint(equalTo: topAnchor), bottomConstraint: tableView.topAnchor.constraint(equalTo: topAnchor)) - tableViewContraints?.activate() + tableViewHeightConstraint = tableView.heightAnchor.constraint(equalTo: heightAnchor) + + NSLayoutConstraint.activate([ + tableViewEdgeContraints.allConstraints, + [tableViewHeightConstraint] + ].flatMap { $0 }) } open override func configureAppearance() { From ec1fe892ad283377cefacf970f3837bb943ea113 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Tue, 30 Aug 2022 14:13:11 +0300 Subject: [PATCH 05/21] fix: merging filters_api --- .../Models/FilterCellStateAppearance.swift | 49 ++++++ .../Models/FilterCellViewModelProtocol.swift | 27 +++ .../DefaultFilterCellViewModel.swift | 45 +++++ .../Views/DefaultFilterCollectionCell.swift | 78 ++++++++ ...Array+FilterPropertyValueRepresenter.swift | 30 ++++ ...UICollectionViewLayout+DefaultLayout.swift | 58 ++++++ .../Models/DefaultFilterPropertyValue.swift | 52 ++++++ .../Models/FiltersLayoutConfiguration.swift | 60 +++++++ .../FilterPropertyValueRepresenter.swift | 27 +++ .../Protocols/FilterViewModelProtocol.swift | 93 ++++++++++ .../ViewModels/BaseFilterViewModel.swift | 83 +++++++++ .../ViewModels/DefaultFilterViewModel.swift | 34 ++++ .../Views/BaseFiltersCollectionView.swift | 166 ++++++++++++++++++ .../Views/DefaultFiltersCollectionView.swift | 24 +++ 14 files changed, 826 insertions(+) create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellStateAppearance.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellViewModelProtocol.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/ViewModels/DefaultFilterCellViewModel.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Views/DefaultFilterCollectionCell.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Helpers/Array+FilterPropertyValueRepresenter.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Helpers/UICollectionViewLayout+DefaultLayout.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/DefaultFilterPropertyValue.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/FiltersLayoutConfiguration.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Protocols/FilterPropertyValueRepresenter.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Protocols/FilterViewModelProtocol.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/ViewModels/BaseFilterViewModel.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/ViewModels/DefaultFilterViewModel.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift create mode 100644 TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/DefaultFiltersCollectionView.swift diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellStateAppearance.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellStateAppearance.swift new file mode 100644 index 00000000..785fbced --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellStateAppearance.swift @@ -0,0 +1,49 @@ +// +// 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 struct FilterCellStateAppearance { + + public let borderColor: UIColor + public let backgroundColor: UIColor + public let fontColor: UIColor + + public let borderWidth: CGFloat + public let contentInsets: UIEdgeInsets + public let cornerRadius: CGFloat + + public init(borderColor: UIColor, + backgroundColor: UIColor, + fontColor: UIColor, + borderWidth: CGFloat, + contentInsets: UIEdgeInsets = .init(top: 4, left: 8, bottom: 4, right: 8), + cornerRadius: CGFloat = 6) { + + self.borderColor = borderColor + self.backgroundColor = backgroundColor + self.fontColor = fontColor + self.borderWidth = borderWidth + self.contentInsets = contentInsets + self.cornerRadius = cornerRadius + } +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellViewModelProtocol.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellViewModelProtocol.swift new file mode 100644 index 00000000..282416d2 --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellViewModelProtocol.swift @@ -0,0 +1,27 @@ +// +// 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. +// + +public protocol FilterCellViewModelProtocol { + var id: String { get } + var title: String { get } + var isSelected: Bool { get set } +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/ViewModels/DefaultFilterCellViewModel.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/ViewModels/DefaultFilterCellViewModel.swift new file mode 100644 index 00000000..e01cae45 --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/ViewModels/DefaultFilterCellViewModel.swift @@ -0,0 +1,45 @@ +// +// 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. +// + +public struct DefaultFilterCellViewModel: FilterCellViewModelProtocol, Hashable { + + public let id: String + public let title: String + public var isSelected: Bool + + public init(id: String, + title: String, + isSelected: Bool) { + + self.id = id + self.title = title + self.isSelected = isSelected + } + + public static func == (lhs: DefaultFilterCellViewModel, rhs: DefaultFilterCellViewModel) -> Bool { + lhs.id == rhs.id + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Views/DefaultFilterCollectionCell.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Views/DefaultFilterCollectionCell.swift new file mode 100644 index 00000000..19895540 --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Views/DefaultFilterCollectionCell.swift @@ -0,0 +1,78 @@ +// +// 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 TIUIKitCore +import TIUIElements +import UIKit + +open class DefaultFilterCollectionCell: ContainerCollectionViewCell, ConfigurableView { + + open var selectedStateAppearance: FilterCellStateAppearance { + .defaultSelectedAppearance + } + + open var normalStateAppearance: FilterCellStateAppearance { + .defaultNormalAppearance + } + + open override var isSelected: Bool { + didSet { + let appearance = isSelected ? selectedStateAppearance : normalStateAppearance + updateAppearance(with: appearance) + } + } + + open override func configureAppearance() { + super.configureAppearance() + + updateAppearance(with: normalStateAppearance) + } + + // MARK: - ConfigurableView + + open func configure(with viewModel: DefaultFilterCellViewModel) { + wrappedView.text = viewModel.title + } + + // MARK: - Open methdos + + open func updateAppearance(with appearance: FilterCellStateAppearance) { + contentInsets = appearance.contentInsets + wrappedView.textColor = appearance.fontColor + + backgroundColor = appearance.backgroundColor + layer.borderColor = appearance.borderColor.cgColor + layer.borderWidth = appearance.borderWidth + layer.round(corners: .allCorners, radius: appearance.cornerRadius) + } +} + +extension FilterCellStateAppearance { + static var defaultSelectedAppearance: FilterCellStateAppearance { + .init(borderColor: .systemGreen, backgroundColor: .white, fontColor: .systemGreen, borderWidth: 1) + } + + static var defaultNormalAppearance: FilterCellStateAppearance { + + .init(borderColor: .lightGray, backgroundColor: .lightGray, fontColor: .black, borderWidth: 0) + } +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Helpers/Array+FilterPropertyValueRepresenter.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Helpers/Array+FilterPropertyValueRepresenter.swift new file mode 100644 index 00000000..1a28fb70 --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Helpers/Array+FilterPropertyValueRepresenter.swift @@ -0,0 +1,30 @@ +// +// 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 extension Array where Element: FilterPropertyValueRepresenter { + + func contains(_ property: Element) -> Bool { + contains(where: { $0.id == property.id }) + } +} \ No newline at end of file diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Helpers/UICollectionViewLayout+DefaultLayout.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Helpers/UICollectionViewLayout+DefaultLayout.swift new file mode 100644 index 00000000..48ca34de --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Helpers/UICollectionViewLayout+DefaultLayout.swift @@ -0,0 +1,58 @@ +// +// 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 +import TIUIKitCore + +@available(iOS 13, *) +public extension UICollectionViewLayout { + + static func gridLayout(_ configuration: FiltersLayoutConfiguration) -> UICollectionViewLayout { + let item = NSCollectionLayoutItem(layoutSize: configuration.itemSize) + + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), + heightDimension: configuration.itemSize.heightDimension) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) + group.interItemSpacing = .fixed(configuration.horizontalItemSpacing) + + let section = NSCollectionLayoutSection(group: group) + section.contentInsets = NSDirectionalEdgeInsets(insets: configuration.contentInsets) + section.interGroupSpacing = configuration.verticalItemSpacing + + return UICollectionViewCompositionalLayout(section: section) + } + + static func horizontalScrollLayout(_ configuration: FiltersLayoutConfiguration) -> UICollectionViewLayout { + let item = NSCollectionLayoutItem(layoutSize: configuration.itemSize) + + let groupSize = NSCollectionLayoutSize(widthDimension: configuration.itemSize.widthDimension, + heightDimension: .fractionalHeight(1)) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) + + let section = NSCollectionLayoutSection(group: group) + section.contentInsets = NSDirectionalEdgeInsets(insets: configuration.contentInsets) + section.interGroupSpacing = configuration.horizontalItemSpacing + section.orthogonalScrollingBehavior = .continuous + + return UICollectionViewCompositionalLayout(section: section) + } +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/DefaultFilterPropertyValue.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/DefaultFilterPropertyValue.swift new file mode 100644 index 00000000..b4bcb5ef --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/DefaultFilterPropertyValue.swift @@ -0,0 +1,52 @@ +// +// 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 struct DefaultFilterPropertyValue: FilterPropertyValueRepresenter { + + public let id: String + public let title: String + public let excludingPropertiesIds: [String] + + public var isSelected: Bool + + public init(id: String, title: String, excludingPropertiesIds: [String] = [], isSelected: Bool = false) { + self.id = id + self.title = title + self.excludingPropertiesIds = excludingPropertiesIds + self.isSelected = isSelected + } +} + +@available(iOS 13, *) +extension DefaultFilterPropertyValue: Identifiable { } + +extension DefaultFilterPropertyValue: Hashable { + public static func == (lhs: DefaultFilterPropertyValue, rhs: DefaultFilterPropertyValue) -> Bool { + lhs.id == rhs.id + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/FiltersLayoutConfiguration.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/FiltersLayoutConfiguration.swift new file mode 100644 index 00000000..06258f52 --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/FiltersLayoutConfiguration.swift @@ -0,0 +1,60 @@ +// +// 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 + +@available(iOS 13, *) +public struct FiltersLayoutConfiguration { + + public var itemSize: NSCollectionLayoutSize + public var horizontalItemSpacing: CGFloat + public var verticalItemSpacing: CGFloat + public var contentInsets: UIEdgeInsets + + public init(itemSize: NSCollectionLayoutSize = .init(widthDimension: .estimated(36), + heightDimension: .estimated(36)), + horizontalItemSpacing: CGFloat = .zero, + verticalItemSpacing: CGFloat = .zero, + contentInsets: UIEdgeInsets) { + + self.itemSize = itemSize + self.horizontalItemSpacing = horizontalItemSpacing + self.verticalItemSpacing = verticalItemSpacing + self.contentInsets = contentInsets + } +} + +@available(iOS 13, *) +public extension FiltersLayoutConfiguration { + static let horizontalScrollConfiguration = FiltersLayoutConfiguration(horizontalItemSpacing: 16, + contentInsets: .init(top: .zero, + left: 8, + bottom: .zero, + right: 8)) + + static let gridConfiguration = FiltersLayoutConfiguration(horizontalItemSpacing: 16, + verticalItemSpacing: 16, + contentInsets: .init(top: .zero, + left: 8, + bottom: .zero, + right: 8)) +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Protocols/FilterPropertyValueRepresenter.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Protocols/FilterPropertyValueRepresenter.swift new file mode 100644 index 00000000..27c9df50 --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Protocols/FilterPropertyValueRepresenter.swift @@ -0,0 +1,27 @@ +// +// 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. +// + +public protocol FilterPropertyValueRepresenter { + var id: String { get } + var excludingPropertiesIds: [String] { get } + var isSelected: Bool { get set } +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Protocols/FilterViewModelProtocol.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Protocols/FilterViewModelProtocol.swift new file mode 100644 index 00000000..afc77da0 --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Protocols/FilterViewModelProtocol.swift @@ -0,0 +1,93 @@ +// +// 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 Foundation +import TISwiftUtils + +public protocol FilterViewModelProtocol: AnyObject { + + associatedtype PropertyValue: FilterPropertyValueRepresenter & Hashable + associatedtype CellViewModelType: FilterCellViewModelProtocol & Hashable + + var values: [PropertyValue] { get set } + var selectedValues: [PropertyValue] { get set } + + func filterDidSelected(atIndexPath indexPath: IndexPath) -> [(indexPath: IndexPath, viewModel: CellViewModelType)] + func toggleProperty(atIndexPath indexPath: IndexPath) -> (selected: [PropertyValue], deselected: [PropertyValue]) +} + +public extension FilterViewModelProtocol { + + func toggleProperty(atIndexPath indexPath: IndexPath) -> (selected: [PropertyValue], deselected: [PropertyValue]) { + guard let item = values[safe: indexPath.item] else { return ([], []) } + + return toggleProperty(item) + } + + @discardableResult + private func toggleProperty(_ value: PropertyValue) -> (selected: [PropertyValue], deselected: [PropertyValue]) { + var valuesToDeselect = [PropertyValue]() + var valuesToSelect = [PropertyValue]() + let selectedValueId = selectedValues.firstIndex { selectedValue in + selectedValue.id == value.id + } + + if let selectedValueId = selectedValueId { + // Removes previously selected filter + selectedValues.remove(at: selectedValueId) + valuesToDeselect.append(value) + } else { + // Selectes unselected filter + selectedValues.append(value) + valuesToSelect.append(value) + + // If the filter has filters to exclude, these filters marks as deselected + let excludedValues = excludeValues(value) + valuesToDeselect.append(contentsOf: excludedValues) + } + + return (valuesToSelect, valuesToDeselect) + } + + private func excludeValues(_ values: PropertyValue) -> [PropertyValue] { + let valuesIdsToExclude = values.excludingPropertiesIds + + guard !valuesIdsToExclude.isEmpty else { + return [] + } + + var excludedValues = [PropertyValue]() + + for valuesIdToExclude in valuesIdsToExclude { + let propertyToExclude = selectedValues.first { property in + property.id == valuesIdToExclude + } + + if let propertyToExclude = propertyToExclude { + let (_, deselected) = toggleProperty(propertyToExclude) + excludedValues.append(contentsOf: deselected) + } + } + + return excludedValues + } +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/ViewModels/BaseFilterViewModel.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/ViewModels/BaseFilterViewModel.swift new file mode 100644 index 00000000..a41f13fd --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/ViewModels/BaseFilterViewModel.swift @@ -0,0 +1,83 @@ +// +// 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 TISwiftUtils +import UIKit + +open class BaseFilterViewModel: FilterViewModelProtocol { + + // MARK: - FilterViewModelProtocol + + public typealias Change = (indexPath: IndexPath, viewModel: CellViewModelType) + + public var values: [PropertyValue] = [] { + didSet { + filtersCollection?.update() + } + } + public var selectedValues: [PropertyValue] = [] { + didSet { + filtersCollection?.update() + } + } + + public weak var filtersCollection: Updatable? + + public private(set) var cellsViewModels: [CellViewModelType] + + public init(filterPropertyValues: [PropertyValue], cellsViewModels: [CellViewModelType] = []) { + self.values = filterPropertyValues + self.cellsViewModels = cellsViewModels + } + + open func filterDidSelected(atIndexPath indexPath: IndexPath) -> [Change] { + let (selected, deselected) = toggleProperty(atIndexPath: indexPath) + + let changedValues = values + .enumerated() + .filter { selected.contains($0.element) || deselected.contains($0.element) } + + changedValues.forEach { index, element in + let isSelected = selectedValues.contains(element) + + setSelectedCell(atIndex: index, isSelected: isSelected) + setSelectedProperty(atIndex: index, isSelected: isSelected) + } + + let changedItems = changedValues + .map { + Change(indexPath: IndexPath(item: $0.offset, section: .zero), + viewModel: cellsViewModels[$0.offset]) + } + + return changedItems + } + + open func setSelectedCell(atIndex index: Int, isSelected: Bool) { + cellsViewModels[index].isSelected = isSelected + } + + open func setSelectedProperty(atIndex index: Int, isSelected: Bool) { + values[index].isSelected = isSelected + } +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/ViewModels/DefaultFilterViewModel.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/ViewModels/DefaultFilterViewModel.swift new file mode 100644 index 00000000..5a668148 --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/ViewModels/DefaultFilterViewModel.swift @@ -0,0 +1,34 @@ +// +// 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. +// + +open class DefaultFilterViewModel: BaseFilterViewModel { + + public init(filterPropertyValues: [DefaultFilterPropertyValue]) { + let cellsViewModel = filterPropertyValues.compactMap { + DefaultFilterCellViewModel(id: $0.id, + title: $0.title, + isSelected: $0.isSelected) + } + + super.init(filterPropertyValues: filterPropertyValues, cellsViewModels: cellsViewModel) + } +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift new file mode 100644 index 00000000..832ffbd1 --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift @@ -0,0 +1,166 @@ +// +// 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 TISwiftUtils +import TIUIKitCore +import UIKit + +@available(iOS 13.0, *) +open class BaseFiltersCollectionView: + UICollectionView, + InitializableViewProtocol, + Updatable, + UICollectionViewDelegate where CellType.ViewModelType: FilterCellViewModelProtocol & Hashable { + + public enum DefaultSection: String { + case main + } + + public typealias DataSource = UICollectionViewDiffableDataSource + public typealias Snapshot = NSDiffableDataSourceSnapshot + + public var layout: UICollectionViewLayout + + public weak var viewModel: BaseFilterViewModel? + + public lazy var collectionViewDataSource = createDataSource() + + // MARK: - Init + + public init(layout: UICollectionViewLayout, viewModel: BaseFilterViewModel? = nil) { + self.layout = layout + self.viewModel = viewModel + + super.init(frame: .zero, collectionViewLayout: layout) + + initializeView() + viewDidLoad() + } + + @available(*, unavailable) + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Life cycle + + open func addViews() { + // override in subclass + } + + open func bindViews() { + delegate = self + } + + open func configureLayout() { + // override in subclass + } + + open func configureAppearance() { + backgroundColor = .white + } + + open func localize() { + // override in subclass + } + + open func viewDidLoad() { + registerCell() + + viewModel?.filtersCollection = self + } + + open func viewDidAppear() { + applySnapshot() + } + + // MARK: - UICollectionViewDelegate + + open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + filterDidTapped(atIndexPath: indexPath) + } + + open func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + filterDidTapped(atIndexPath: indexPath) + } + + // MARK: - UpdatableView + + open func update() { + applySnapshot() + } + + // MARK: - Open methods + + open func registerCell() { + register(CellType.self, forCellWithReuseIdentifier: CellType.reuseIdentifier) + } + + open func filterDidTapped(atIndexPath indexPath: IndexPath) { + guard let viewModel = viewModel else { return } + + let changes = viewModel.filterDidSelected(atIndexPath: indexPath) + + applyChange(changes) + } + + open func applySnapshot() { + guard let viewModel = viewModel else { + return + } + + var snapshot = Snapshot() + + snapshot.appendSections([DefaultSection.main.rawValue]) + snapshot.appendItems(viewModel.cellsViewModels, toSection: DefaultSection.main.rawValue) + + collectionViewDataSource.apply(snapshot, animatingDifferences: true) + } + + open func createDataSource() -> DataSource { + let cellProvider: DataSource.CellProvider = { collectionView, indexPath, itemIdentifier in + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellType.reuseIdentifier, + for: indexPath) as? CellType + + cell?.configure(with: itemIdentifier) + + return cell + } + + return DataSource(collectionView: self, cellProvider: cellProvider) + } + + open func applyChange(_ changes: [BaseFilterViewModel.Change]) { + changes.forEach { change in + guard let cell = cellForItem(at: change.indexPath) as? CellType else { + return + } + + cell.configure(with: change.viewModel) + + change.viewModel.isSelected + ? selectItem(at: change.indexPath, animated: false, scrollPosition: []) + : deselectItem(at: change.indexPath, animated: false) + } + } +} diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/DefaultFiltersCollectionView.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/DefaultFiltersCollectionView.swift new file mode 100644 index 00000000..8caef21e --- /dev/null +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/DefaultFiltersCollectionView.swift @@ -0,0 +1,24 @@ +// +// 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. +// + +@available(iOS 13.0, *) +public typealias DefaultFiltersCollectionView = BaseFiltersCollectionView From 0bc8574a32673fe0910acdb91af4ccd89c3f3254 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Tue, 30 Aug 2022 21:34:29 +0300 Subject: [PATCH 06/21] fix: update table director with diffable data source --- .../DefaultFilterCellViewModel.swift | 0 .../Models/DefaultFilterRowViewModel.swift | 43 ----- .../FilterListPickerConfigurator.swift | 20 +-- .../Protocols/FilterTableSectionBuilder.swift | 10 +- .../Protocols/FiltersPickerDelegate.swift | 2 +- .../ViewModels/BaseListFilterViewModel.swift | 142 ++++++++--------- .../DefaultFilterListSectionBuilder.swift | 46 +++--- .../Views/BaseListFilterPickerView.swift | 147 +++++++++++++++--- .../Views/DefaultFilterListCell.swift | 144 ++++++++++------- .../DefaultFilterTableView.swift} | 13 +- .../Models/FilterCellStateAppearance.swift | 6 +- .../Views/BaseFiltersCollectionView.swift | 8 +- .../Protocols/ReuseIdentifierProtocol.swift | 6 + 13 files changed, 339 insertions(+), 248 deletions(-) rename TIEcommerce/Sources/Filters/{TagsFilters/FiltersCollectionCell => Cells}/ViewModels/DefaultFilterCellViewModel.swift (100%) delete mode 100644 TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift rename TIEcommerce/Sources/Filters/ListFilters/{Protocols/FilterRowRepresentable.swift => Views/DefaultFilterTableView.swift} (77%) diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/ViewModels/DefaultFilterCellViewModel.swift b/TIEcommerce/Sources/Filters/Cells/ViewModels/DefaultFilterCellViewModel.swift similarity index 100% rename from TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/ViewModels/DefaultFilterCellViewModel.swift rename to TIEcommerce/Sources/Filters/Cells/ViewModels/DefaultFilterCellViewModel.swift diff --git a/TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift b/TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift deleted file mode 100644 index 0e2e0643..00000000 --- a/TIEcommerce/Sources/Filters/ListFilters/Models/DefaultFilterRowViewModel.swift +++ /dev/null @@ -1,43 +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. -// - -public struct DefaultFilterRowViewModel: FilterRowRepresentable, Equatable { - public let id: String - public let title: String - public var appearance: FilterCellAppearanceProtocol - public var isSelected: Bool - - public init(id: String, - title: String, - appearance: FilterCellAppearanceProtocol, - isSelected: Bool) { - - self.id = id - self.title = title - self.appearance = appearance - self.isSelected = isSelected - } - - public static func == (lhs: Self, rhs: Self) -> Bool { - lhs.id == rhs.id - } -} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift index 55b6f91e..87ab5086 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift @@ -20,13 +20,13 @@ // THE SOFTWARE. // -public protocol FilterListPickerConfigurator: AnyObject { - - associatedtype RowViewModel: FilterRowRepresentable & Equatable - - var visibleValues: [RowViewModel] { get } - var isMultiselectionEnabled: Bool { get set } - var isFinishWithSeparator: Bool { get } - - func setSelected(model: RowViewModel, isSelected: Bool) -} +//public protocol FilterListPickerConfigurator: AnyObject { +// +// associatedtype RowViewModel: FilterRowRepresentable & Equatable +// +// var visibleValues: [RowViewModel] { get } +// var isMultiselectionEnabled: Bool { get set } +// var isFinishWithSeparator: Bool { get } +// +// func setSelected(model: RowViewModel, isSelected: Bool) +//} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift index 998e735a..556260b0 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift @@ -23,8 +23,8 @@ import TableKit import TIUIElements -public protocol FilterTableSectionBuilder { - associatedtype CellType: BaseSeparatorCell & ConfigurableCell - - func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.RowViewModel == CellType.CellData -} +//public protocol FilterTableSectionBuilder { +// associatedtype CellType: BaseSeparatorCell & ConfigurableCell +// +// func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.RowViewModel == CellType.CellData +//} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FiltersPickerDelegate.swift b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FiltersPickerDelegate.swift index a7008e31..29634087 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FiltersPickerDelegate.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FiltersPickerDelegate.swift @@ -21,5 +21,5 @@ // public protocol FiltersPickerDelegate: AnyObject { - func filters(didSelect filters: [FilterRowRepresentable]) + func filters(didSelect filters: [FilterPropertyValueRepresenter]) } diff --git a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift index 4df0a8cd..74e003d2 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift @@ -20,74 +20,74 @@ // THE SOFTWARE. // -open class BaseListFilterViewModel: FilterListPickerConfigurator { - - public typealias RowViewModel = RowViewModelType - - public var rowViewModels: [RowViewModelType] - - public weak var delegate: FiltersPickerDelegate? - - public var initiallySelectedValues: [RowViewModelType]? - - open var isFinishWithSeparator: Bool { - true - } - - open var isMultiselectionEnabled: Bool = false - - open var areInitiallySelectedValuesTheSame: Bool { - guard let initiallySelectedValues = initiallySelectedValues else { - return false - } - - return initiallySelectedValues == selectedValues - } - - open var areThereAnyValuesSelected: Bool { - if let _ = rowViewModels.first(where: { $0.isSelected }) { - return false - } - - return true - } - - open var selectedValues: [RowViewModelType] { - rowViewModels.filter { $0.isSelected } - } - - open var visibleValues: [RowViewModelType] { - rowViewModels - } - - open var visibleSelectedIndexes: [Int] { - visibleValues.enumerated().compactMap { - $0.element.isSelected ? $0.offset : nil - } - } - - public init(rowViewModels: [RowViewModelType], isMultiselectionEnabled: Bool) { - self.rowViewModels = rowViewModels - self.isMultiselectionEnabled = isMultiselectionEnabled - } - - open func viewDidLoad() { - initiallySelectedValues = selectedValues - } - - open func setSelected(model: RowViewModelType, isSelected: Bool) { - if !isMultiselectionEnabled { - rowViewModels.enumerated().forEach { - rowViewModels[$0.offset].isSelected = false - } - } - - if let index = rowViewModels.firstIndex(of: model) { - rowViewModels[index].isSelected = isSelected - } - - if !selectedValues.isEmpty { - delegate?.filters(didSelect: selectedValues) - } - } -} +//open class BaseListFilterViewModel: FilterListPickerConfigurator { +// +// public typealias RowViewModel = RowViewModelType +// +// public var rowViewModels: [RowViewModelType] +// +// public weak var delegate: FiltersPickerDelegate? +// +// public var initiallySelectedValues: [RowViewModelType]? +// +// open var isFinishWithSeparator: Bool { +// true +// } +// +// open var isMultiselectionEnabled: Bool = false +// +// open var areInitiallySelectedValuesTheSame: Bool { +// guard let initiallySelectedValues = initiallySelectedValues else { +// return false +// } +// +// return initiallySelectedValues == selectedValues +// } +// +// open var areThereAnyValuesSelected: Bool { +// if let _ = rowViewModels.first(where: { $0.isSelected }) { +// return false +// } +// +// return true +// } +// +// open var selectedValues: [RowViewModelType] { +// rowViewModels.filter { $0.isSelected } +// } +// +// open var visibleValues: [RowViewModelType] { +// rowViewModels +// } +// +// open var visibleSelectedIndexes: [Int] { +// visibleValues.enumerated().compactMap { +// $0.element.isSelected ? $0.offset : nil +// } +// } +// +// public init(rowViewModels: [RowViewModelType], isMultiselectionEnabled: Bool) { +// self.rowViewModels = rowViewModels +// self.isMultiselectionEnabled = isMultiselectionEnabled +// } +// +// open func viewDidLoad() { +// initiallySelectedValues = selectedValues +// } +// +// open func setSelected(model: RowViewModelType, isSelected: Bool) { +// if !isMultiselectionEnabled { +// rowViewModels.enumerated().forEach { +// rowViewModels[$0.offset].isSelected = false +// } +// } +// +// if let index = rowViewModels.firstIndex(of: model) { +// rowViewModels[index].isSelected = isSelected +// } +// +// if !selectedValues.isEmpty { +// delegate?.filters(didSelect: selectedValues) +// } +// } +//} diff --git a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift index 4ec41d68..91af7939 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift @@ -24,26 +24,26 @@ import TableKit import TITableKitUtils import TIUIElements -open class DefaultFilterListSectionBuilder: FilterTableSectionBuilder { - - open func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.RowViewModel == CellType.CellData { - let rows = viewModel.visibleValues.map { item in - TableRow(item: item) - .on(.select) { [weak viewModel] _ in - viewModel?.setSelected(model: item, isSelected: !item.isSelected) - } - .on(.deselect) { [weak viewModel] _ in - viewModel?.setSelected(model: item, isSelected: false) - } - } - - let separatedRows: [SeparatorRowBox] = rows.map { $0.separatorRowBox } - let separator = SeparatorConfiguration(color: .gray) - - separatedRows.configureSeparators(first: separator, - middle: separator, - last: viewModel.isFinishWithSeparator ? separator : SeparatorConfiguration(color: .clear)) - - return .init(rows: separatedRows.rows) - } -} +//open class DefaultFilterListSectionBuilder: FilterTableSectionBuilder { +// +// open func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.RowViewModel == CellType.CellData { +// let rows = viewModel.visibleValues.map { item in +// TableRow(item: item) +// .on(.select) { [weak viewModel] _ in +// viewModel?.setSelected(model: item, isSelected: !item.isSelected) +// } +// .on(.deselect) { [weak viewModel] _ in +// viewModel?.setSelected(model: item, isSelected: false) +// } +// } +// +// let separatedRows: [SeparatorRowBox] = rows.map { $0.separatorRowBox } +// let separator = SeparatorConfiguration(color: .gray) +// +// separatedRows.configureSeparators(first: separator, +// middle: separator, +// last: viewModel.isFinishWithSeparator ? separator : SeparatorConfiguration(color: .clear)) +// +// return .init(rows: separatedRows.rows) +// } +//} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift index fcafc5f7..1a8b4427 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift @@ -20,46 +20,147 @@ // THE SOFTWARE. // -import TableKit +import TISwiftUtils import TIUIElements +import TIUIKitCore import UIKit -open class BaseListFilterPickerView: BaseCustomTableView where CellType.CellData: FilterRowRepresentable & Equatable { +@available(iOS 13.0, *) +open class BaseFiltersTableView: + UITableView, + InitializableViewProtocol, + Updatable, + UITableViewDelegate where CellType.ViewModelType: FilterCellViewModelProtocol & Hashable { - public let builder = DefaultFilterListSectionBuilder() - public weak var viewModel: BaseListFilterViewModel? - - public init(viewModel: BaseListFilterViewModel) { - self.viewModel = viewModel - - super.init(frame: .zero) - - tableView.allowsMultipleSelection = viewModel.isMultiselectionEnabled + public enum DefaultSection: String { + case main } + public typealias DataSource = UITableViewDiffableDataSource + public typealias Snapshot = NSDiffableDataSourceSnapshot + + public weak var viewModel: BaseFilterViewModel? + + public lazy var collectionViewDataSource = createDataSource() + + // MARK: - Init + + public init(viewModel: BaseFilterViewModel, allowsMultipleSelection: Bool = true) { + self.viewModel = viewModel + + super.init(frame: .zero, style: .plain) + + self.allowsMultipleSelection = allowsMultipleSelection + } + + @available(*, unavailable) required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - open override func configureAppearance() { - super.configureAppearance() + // MARK: - Life cycle - tableView.alwaysBounceVertical = false - viewModel?.viewDidLoad() + // MARK: - Life cycle - reloadData(with: viewModel?.visibleSelectedIndexes ?? []) + open func addViews() { + // override in subclass } - open func reloadData(with selectedIndexes: [Int]) { + open func configureLayout() { + // override in subclass + } + + open func bindViews() { + delegate = self + } + + open func configureAppearance() { + alwaysBounceVertical = false + +// reloadData(with: viewModel?.visibleSelectedIndexes ?? []) + } + + open func localize() { + // override in subclass + } + + open func viewDidLoad() { + registerCell() + + viewModel?.filtersCollection = self + } + + open func viewDidAppear() { + applySnapshot() + } + + // MARK: - UICollectionViewDelegate + + open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + filterDidTapped(atIndexPath: indexPath) + } + + open func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + filterDidTapped(atIndexPath: indexPath) + } + + // MARK: - UpdatableView + + open func update() { + applySnapshot() + } + + // MARK: - Open methods + + open func registerCell() { + register(CellType.self, forCellReuseIdentifier: CellType.reuseIdentifier) + } + + open func filterDidTapped(atIndexPath indexPath: IndexPath) { guard let viewModel = viewModel else { return } - let elements = builder.makeSection(with: viewModel) - updateTableView(elements) + let changes = viewModel.filterDidSelected(atIndexPath: indexPath) - selectedIndexes.forEach { - tableView.selectRow(at: IndexPath(row: $0, section: .zero), - animated: false, - scrollPosition: .none) + applyChange(changes) + } + + open func applySnapshot() { + guard let viewModel = viewModel else { + return + } + + var snapshot = Snapshot() + + snapshot.appendSections([DefaultSection.main.rawValue]) + snapshot.appendItems(viewModel.cellsViewModels, toSection: DefaultSection.main.rawValue) + + collectionViewDataSource.apply(snapshot, animatingDifferences: true) + } + + open func createDataSource() -> DataSource { + let cellProvider: DataSource.CellProvider = { tableView, indexPath, itemIdentifier in + let cell = tableView.dequeueReusableCell(withIdentifier: CellType.reuseIdentifier, for: indexPath) as? CellType + + cell?.configure(with: itemIdentifier) + + return cell + } + + return DataSource(tableView: self, cellProvider: cellProvider) + } + + open func applyChange(_ changes: [BaseFilterViewModel.Change]) { + changes.forEach { change in + guard let cell = cellForRow(at: change.indexPath) as? CellType else { + return + } + + cell.configure(with: change.viewModel) + + change.viewModel.isSelected + ? selectRow(at: change.indexPath, animated: false, scrollPosition: ScrollPosition.none) + : deselectRow(at: change.indexPath, animated: false) } } } diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift index ef149287..66a464b1 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift @@ -20,91 +20,123 @@ // THE SOFTWARE. // -import TableKit import TIUIElements +import TIUIKitCore import UIKit -open class DefaultFilterListCell: BaseSeparatorCell, ConfigurableCell { +open class DefaultPickerView: BaseInitializableView { - private var titleLeadingConstraint: NSLayoutConstraint! - private var titleTopConstraint: NSLayoutConstraint! - private var titleBottomConstraint: NSLayoutConstraint! + private let titleLabel = UILabel() + private let selectionStateImageView = UIImageView() - private var imageTrailingConstraint: NSLayoutConstraint! - private var imageTopConstraint: NSLayoutConstraint! - private var imageBottomConstraint: NSLayoutConstraint! - - public lazy var titleLabel = UILabel() - public lazy var selectionCheckmarkImageView = UIImageView() - - open var selectedImage: UIImage? { - if #available(iOS 13.0, *) { - return UIImage(systemName: "checkmark")! + open var image: UIImage? { + get { + selectionStateImageView.image + } + set { + selectionStateImageView.image = newValue } - - return nil } - open var deselectedImage: UIImage? { - nil + open var text: String? { + get { + titleLabel.text + } + set { + titleLabel.text = newValue + } + } + + open var textColor: UIColor { + get { + titleLabel.textColor + } + set { + titleLabel.textColor = newValue + } } open override func addViews() { super.addViews() - contentView.addSubviews(titleLabel, selectionCheckmarkImageView) + addSubviews(titleLabel, selectionStateImageView) } open override func configureLayout() { super.configureLayout() titleLabel.translatesAutoresizingMaskIntoConstraints = false - selectionCheckmarkImageView.translatesAutoresizingMaskIntoConstraints = false - - titleLeadingConstraint = titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor) - titleTopConstraint = titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor) - titleBottomConstraint = titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) - - imageTrailingConstraint = selectionCheckmarkImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) - imageTopConstraint = selectionCheckmarkImageView.topAnchor.constraint(equalTo: contentView.topAnchor) - imageBottomConstraint = selectionCheckmarkImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) - + selectionStateImageView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ - titleLeadingConstraint, - titleTopConstraint, - titleBottomConstraint, - imageTrailingConstraint, - imageTopConstraint, - imageBottomConstraint + titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor), + titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor), + titleLabel.heightAnchor.constraint(equalTo: heightAnchor), + + selectionStateImageView.trailingAnchor.constraint(equalTo: trailingAnchor), + selectionStateImageView.centerYAnchor.constraint(equalTo: centerYAnchor), + selectionStateImageView.heightAnchor.constraint(equalTo: heightAnchor), ]) } +} - open func configure(with viewModel: DefaultFilterRowViewModel) { - titleLabel.text = viewModel.title +open class DefaultFilterListCell: ContainerTableViewCell, ConfigurableView { - setContentInsets(viewModel.appearance.contentInsets) - contentView.backgroundColor = viewModel.appearance.deselectedBgColor + open var selectedStateAppearance: FilterCellStateAppearance { + .defaultSelectedRowAppearance } - open func setContentInsets(_ insets: UIEdgeInsets) { - titleLeadingConstraint.constant = insets.left - imageTrailingConstraint.constant = -insets.right - - titleTopConstraint.constant = insets.top - imageTopConstraint.constant = insets.top - - titleBottomConstraint.constant = -insets.bottom - imageBottomConstraint.constant = -insets.bottom + open var normalStateAppearance: FilterCellStateAppearance { + .defaultNormalRowAppearance } - open override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - - if selected { - selectionCheckmarkImageView.image = selectedImage - } else { - selectionCheckmarkImageView.image = deselectedImage + open override var isSelected: Bool { + didSet { + let appearance = isSelected ? selectedStateAppearance : normalStateAppearance + updateAppearance(with: appearance) } } + + open override func configureAppearance() { + super.configureAppearance() + + updateAppearance(with: normalStateAppearance) + } + + open func configure(with viewModel: DefaultFilterCellViewModel) { + wrappedView.text = viewModel.title + } + + // MARK: - Open methods + + open func updateAppearance(with appearance: FilterCellStateAppearance) { + contentInsets = appearance.contentInsets + wrappedView.textColor = appearance.fontColor + + backgroundColor = appearance.backgroundColor + layer.borderColor = appearance.borderColor.cgColor + layer.borderWidth = appearance.borderWidth + layer.round(corners: .allCorners, radius: appearance.cornerRadius) + } +} + +extension FilterCellStateAppearance { + static var defaultSelectedRowAppearance: FilterCellStateAppearance { + var selectionImage: UIImage? + + if #available(iOS 13, *) { + selectionImage = UIImage(systemName: "checkmark") + } + + return .init(borderColor: .systemGreen, + backgroundColor: .white, + fontColor: .systemGreen, + borderWidth: 1, + selectionImage: selectionImage) + } + + static var defaultNormalRowAppearance: FilterCellStateAppearance { + + .init(borderColor: .lightGray, backgroundColor: .lightGray, fontColor: .black, borderWidth: 0) + } } diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterRowRepresentable.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterTableView.swift similarity index 77% rename from TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterRowRepresentable.swift rename to TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterTableView.swift index 74467f5f..5eff8890 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterRowRepresentable.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterTableView.swift @@ -20,14 +20,5 @@ // THE SOFTWARE. // -public protocol FilterRowRepresentable { - var id: String { get } - var title: String { get } - var isSelected: Bool { get set } - var appearance: FilterCellAppearanceProtocol { get set } - - init(id: String, - title: String, - appearance: FilterCellAppearanceProtocol, - isSelected: Bool) -} +@available(iOS 13.0, *) +public typealias DefaultFiltersTableView = BaseFiltersTableView diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellStateAppearance.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellStateAppearance.swift index 785fbced..74763640 100644 --- a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellStateAppearance.swift +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellStateAppearance.swift @@ -32,12 +32,15 @@ public struct FilterCellStateAppearance { public let contentInsets: UIEdgeInsets public let cornerRadius: CGFloat + public let selectionImage: UIImage? + public init(borderColor: UIColor, backgroundColor: UIColor, fontColor: UIColor, borderWidth: CGFloat, contentInsets: UIEdgeInsets = .init(top: 4, left: 8, bottom: 4, right: 8), - cornerRadius: CGFloat = 6) { + cornerRadius: CGFloat = 6, + selectionImage: UIImage? = nil) { self.borderColor = borderColor self.backgroundColor = backgroundColor @@ -45,5 +48,6 @@ public struct FilterCellStateAppearance { self.borderWidth = borderWidth self.contentInsets = contentInsets self.cornerRadius = cornerRadius + self.selectionImage = selectionImage } } diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift index 832ffbd1..81073b0b 100644 --- a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift +++ b/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift @@ -68,14 +68,14 @@ open class BaseFiltersCollectionView Date: Tue, 30 Aug 2022 21:42:51 +0300 Subject: [PATCH 07/21] fix: initialisation of table view logic --- .../ListFilters/Views/BaseListFilterPickerView.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift index 1a8b4427..c997e852 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift @@ -42,7 +42,7 @@ open class BaseFiltersTableView? - public lazy var collectionViewDataSource = createDataSource() + public lazy var tableViewDataSource = createDataSource() // MARK: - Init @@ -51,7 +51,8 @@ open class BaseFiltersTableView DataSource { From d16fc21d7efcefa29cd1d5bff10aacb55f9fed39 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Tue, 30 Aug 2022 21:45:36 +0300 Subject: [PATCH 08/21] fix: table view delegate methods --- .../ListFilters/Views/BaseListFilterPickerView.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift index c997e852..31bc9813 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift @@ -62,8 +62,6 @@ open class BaseFiltersTableView Date: Tue, 30 Aug 2022 21:51:16 +0300 Subject: [PATCH 09/21] fix: image of selected view --- .../Filters/ListFilters/Views/DefaultFilterListCell.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift index 66a464b1..dd4dc077 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift @@ -112,6 +112,7 @@ open class DefaultFilterListCell: ContainerTableViewCell, Con open func updateAppearance(with appearance: FilterCellStateAppearance) { contentInsets = appearance.contentInsets wrappedView.textColor = appearance.fontColor + wrappedView.image = appearance.selectionImage backgroundColor = appearance.backgroundColor layer.borderColor = appearance.borderColor.cgColor From fa130aecdbf8e35dc6e505cc091f1aea8903f393 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Tue, 30 Aug 2022 21:58:24 +0300 Subject: [PATCH 10/21] fix: style in init of table view --- .../ListFilters/Views/BaseListFilterPickerView.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift index 31bc9813..bd34638d 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift @@ -46,10 +46,13 @@ open class BaseFiltersTableView, allowsMultipleSelection: Bool = true) { + public init(viewModel: BaseFilterViewModel, + allowsMultipleSelection: Bool = true, + style: UITableView.Style = .plain) { + self.viewModel = viewModel - super.init(frame: .zero, style: .plain) + super.init(frame: .zero, style: style) initializeView() viewDidLoad() From 8cbf5b96e68a9bdfcf32e978be3c6bc926a20c53 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Tue, 30 Aug 2022 22:01:06 +0300 Subject: [PATCH 11/21] fix: container table view cell subviews --- TIUIElements/Sources/Wrappers/ContainerTableViewCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TIUIElements/Sources/Wrappers/ContainerTableViewCell.swift b/TIUIElements/Sources/Wrappers/ContainerTableViewCell.swift index f22aed9e..3892e327 100644 --- a/TIUIElements/Sources/Wrappers/ContainerTableViewCell.swift +++ b/TIUIElements/Sources/Wrappers/ContainerTableViewCell.swift @@ -41,7 +41,7 @@ open class ContainerTableViewCell: BaseInitializableCell, WrappedV override open func addViews() { super.addViews() - addSubview(wrappedView) + contentView.addSubview(wrappedView) } override open func configureLayout() { From eaa787e00f52bc4b32a0d5610e968adb9c84b528 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Tue, 30 Aug 2022 22:04:44 +0300 Subject: [PATCH 12/21] fix: image view size determination --- .../Filters/ListFilters/Views/DefaultFilterListCell.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift index dd4dc077..76d910f9 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift @@ -56,6 +56,10 @@ open class DefaultPickerView: BaseInitializableView { } } + open var defaultImageSize: CGFloat { + image?.size.height ?? 0 + } + open override func addViews() { super.addViews() @@ -75,7 +79,8 @@ open class DefaultPickerView: BaseInitializableView { selectionStateImageView.trailingAnchor.constraint(equalTo: trailingAnchor), selectionStateImageView.centerYAnchor.constraint(equalTo: centerYAnchor), - selectionStateImageView.heightAnchor.constraint(equalTo: heightAnchor), + selectionStateImageView.heightAnchor.constraint(equalToConstant: defaultImageSize) + selectionStateImageView.widthAnchor.constraint(equalToConstant: defaultImageSize) ]) } } From 77f6208ff5f4296381a631d0da9e4b1d1828005b Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Wed, 31 Aug 2022 17:52:56 +0300 Subject: [PATCH 13/21] fix: logic of picking cells + refactoring --- .../Models/FilterCellStateAppearance.swift | 0 .../FilterCellViewModelProtocol.swift | 0 .../BaseFiltersTableView.swift} | 6 +- .../DefaultFilterTableView.swift | 2 +- .../DefaultFilterTableViewCell.swift | 93 +++++++++++++++++++ .../DefaultPickerView.swift} | 65 +------------ .../FilterListPickerConfigurator.swift | 32 ------- .../ViewModels/BaseListFilterViewModel.swift | 93 ------------------- .../DefaultFilterListSectionBuilder.swift | 49 ---------- .../Views/BaseCustomTableView.swift | 82 ---------------- .../Protocols/FiltersPickerDelegate.swift | 0 .../Sources/Protocols/Selectable.swift | 22 +++-- 12 files changed, 114 insertions(+), 330 deletions(-) rename TIEcommerce/Sources/Filters/{TagsFilters/FiltersCollectionCell => Cells}/Models/FilterCellStateAppearance.swift (100%) rename TIEcommerce/Sources/Filters/{TagsFilters/FiltersCollectionCell/Models => Cells/Protocols}/FilterCellViewModelProtocol.swift (100%) rename TIEcommerce/Sources/Filters/ListFilters/{Views/BaseListFilterPickerView.swift => FiltersTableView/BaseFiltersTableView.swift} (97%) rename TIEcommerce/Sources/Filters/ListFilters/{Views => FiltersTableView}/DefaultFilterTableView.swift (95%) create mode 100644 TIEcommerce/Sources/Filters/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift rename TIEcommerce/Sources/Filters/ListFilters/{Views/DefaultFilterListCell.swift => FiltersTableViewCell/DefaultPickerView.swift} (58%) delete mode 100644 TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift delete mode 100644 TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift delete mode 100644 TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift delete mode 100644 TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift rename TIEcommerce/Sources/Filters/{ListFilters => }/Protocols/FiltersPickerDelegate.swift (100%) rename TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift => TISwiftUtils/Sources/Protocols/Selectable.swift (76%) diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellStateAppearance.swift b/TIEcommerce/Sources/Filters/Cells/Models/FilterCellStateAppearance.swift similarity index 100% rename from TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellStateAppearance.swift rename to TIEcommerce/Sources/Filters/Cells/Models/FilterCellStateAppearance.swift diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellViewModelProtocol.swift b/TIEcommerce/Sources/Filters/Cells/Protocols/FilterCellViewModelProtocol.swift similarity index 100% rename from TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Models/FilterCellViewModelProtocol.swift rename to TIEcommerce/Sources/Filters/Cells/Protocols/FilterCellViewModelProtocol.swift diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift b/TIEcommerce/Sources/Filters/ListFilters/FiltersTableView/BaseFiltersTableView.swift similarity index 97% rename from TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift rename to TIEcommerce/Sources/Filters/ListFilters/FiltersTableView/BaseFiltersTableView.swift index bd34638d..082499f0 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseListFilterPickerView.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/FiltersTableView/BaseFiltersTableView.swift @@ -80,8 +80,6 @@ open class BaseFiltersTableView +public typealias DefaultFiltersTableView = BaseFiltersTableView diff --git a/TIEcommerce/Sources/Filters/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift b/TIEcommerce/Sources/Filters/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift new file mode 100644 index 00000000..70878e02 --- /dev/null +++ b/TIEcommerce/Sources/Filters/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift @@ -0,0 +1,93 @@ +// +// 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 TISwiftUtils +import TIUIElements +import TIUIKitCore +import UIKit + +open class DefaultFilterTableViewCell: ContainerTableViewCell, ConfigurableView, Selectable { + + open var selectedStateAppearance: FilterCellStateAppearance { + .defaultSelectedRowAppearance + } + + open var normalStateAppearance: FilterCellStateAppearance { + .defaultNormalRowAppearance + } + + open override var isSelected: Bool { + didSet { + let appearance = isSelected ? selectedStateAppearance : normalStateAppearance + updateAppearance(with: appearance) + } + } + + // MARK: Life cycle + + open override func configureAppearance() { + super.configureAppearance() + + updateAppearance(with: normalStateAppearance) + } + + // MARK: - ConfigurableView + + open func configure(with viewModel: DefaultFilterCellViewModel) { + wrappedView.text = viewModel.title + } + + // MARK: - Open methods + + open func updateAppearance(with appearance: FilterCellStateAppearance) { + contentInsets = appearance.contentInsets + wrappedView.textColor = appearance.fontColor + wrappedView.image = appearance.selectionImage + + backgroundColor = appearance.backgroundColor + layer.borderColor = appearance.borderColor.cgColor + layer.borderWidth = appearance.borderWidth + layer.round(corners: .allCorners, radius: appearance.cornerRadius) + } +} + +extension FilterCellStateAppearance { + + static var defaultSelectedRowAppearance: FilterCellStateAppearance { + var selectionImage: UIImage? + + if #available(iOS 13, *) { + selectionImage = UIImage(systemName: "checkmark") + } + + return .init(borderColor: .clear, + backgroundColor: .white, + fontColor: .black, + borderWidth: .zero, + cornerRadius: .zero, + selectionImage: selectionImage) + } + + static var defaultNormalRowAppearance: FilterCellStateAppearance { + .init(borderColor: .clear, backgroundColor: .white, fontColor: .black, borderWidth: .zero, cornerRadius: .zero, selectionImage: nil) + } +} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift b/TIEcommerce/Sources/Filters/ListFilters/FiltersTableViewCell/DefaultPickerView.swift similarity index 58% rename from TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift rename to TIEcommerce/Sources/Filters/ListFilters/FiltersTableViewCell/DefaultPickerView.swift index 76d910f9..b18e9836 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Views/DefaultFilterListCell.swift +++ b/TIEcommerce/Sources/Filters/ListFilters/FiltersTableViewCell/DefaultPickerView.swift @@ -21,7 +21,6 @@ // import TIUIElements -import TIUIKitCore import UIKit open class DefaultPickerView: BaseInitializableView { @@ -79,70 +78,8 @@ open class DefaultPickerView: BaseInitializableView { selectionStateImageView.trailingAnchor.constraint(equalTo: trailingAnchor), selectionStateImageView.centerYAnchor.constraint(equalTo: centerYAnchor), - selectionStateImageView.heightAnchor.constraint(equalToConstant: defaultImageSize) + selectionStateImageView.heightAnchor.constraint(equalToConstant: defaultImageSize), selectionStateImageView.widthAnchor.constraint(equalToConstant: defaultImageSize) ]) } } - -open class DefaultFilterListCell: ContainerTableViewCell, ConfigurableView { - - open var selectedStateAppearance: FilterCellStateAppearance { - .defaultSelectedRowAppearance - } - - open var normalStateAppearance: FilterCellStateAppearance { - .defaultNormalRowAppearance - } - - open override var isSelected: Bool { - didSet { - let appearance = isSelected ? selectedStateAppearance : normalStateAppearance - updateAppearance(with: appearance) - } - } - - open override func configureAppearance() { - super.configureAppearance() - - updateAppearance(with: normalStateAppearance) - } - - open func configure(with viewModel: DefaultFilterCellViewModel) { - wrappedView.text = viewModel.title - } - - // MARK: - Open methods - - open func updateAppearance(with appearance: FilterCellStateAppearance) { - contentInsets = appearance.contentInsets - wrappedView.textColor = appearance.fontColor - wrappedView.image = appearance.selectionImage - - backgroundColor = appearance.backgroundColor - layer.borderColor = appearance.borderColor.cgColor - layer.borderWidth = appearance.borderWidth - layer.round(corners: .allCorners, radius: appearance.cornerRadius) - } -} - -extension FilterCellStateAppearance { - static var defaultSelectedRowAppearance: FilterCellStateAppearance { - var selectionImage: UIImage? - - if #available(iOS 13, *) { - selectionImage = UIImage(systemName: "checkmark") - } - - return .init(borderColor: .systemGreen, - backgroundColor: .white, - fontColor: .systemGreen, - borderWidth: 1, - selectionImage: selectionImage) - } - - static var defaultNormalRowAppearance: FilterCellStateAppearance { - - .init(borderColor: .lightGray, backgroundColor: .lightGray, fontColor: .black, borderWidth: 0) - } -} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift b/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift deleted file mode 100644 index 87ab5086..00000000 --- a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterListPickerConfigurator.swift +++ /dev/null @@ -1,32 +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. -// - -//public protocol FilterListPickerConfigurator: AnyObject { -// -// associatedtype RowViewModel: FilterRowRepresentable & Equatable -// -// var visibleValues: [RowViewModel] { get } -// var isMultiselectionEnabled: Bool { get set } -// var isFinishWithSeparator: Bool { get } -// -// func setSelected(model: RowViewModel, isSelected: Bool) -//} diff --git a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift deleted file mode 100644 index 74e003d2..00000000 --- a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/BaseListFilterViewModel.swift +++ /dev/null @@ -1,93 +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. -// - -//open class BaseListFilterViewModel: FilterListPickerConfigurator { -// -// public typealias RowViewModel = RowViewModelType -// -// public var rowViewModels: [RowViewModelType] -// -// public weak var delegate: FiltersPickerDelegate? -// -// public var initiallySelectedValues: [RowViewModelType]? -// -// open var isFinishWithSeparator: Bool { -// true -// } -// -// open var isMultiselectionEnabled: Bool = false -// -// open var areInitiallySelectedValuesTheSame: Bool { -// guard let initiallySelectedValues = initiallySelectedValues else { -// return false -// } -// -// return initiallySelectedValues == selectedValues -// } -// -// open var areThereAnyValuesSelected: Bool { -// if let _ = rowViewModels.first(where: { $0.isSelected }) { -// return false -// } -// -// return true -// } -// -// open var selectedValues: [RowViewModelType] { -// rowViewModels.filter { $0.isSelected } -// } -// -// open var visibleValues: [RowViewModelType] { -// rowViewModels -// } -// -// open var visibleSelectedIndexes: [Int] { -// visibleValues.enumerated().compactMap { -// $0.element.isSelected ? $0.offset : nil -// } -// } -// -// public init(rowViewModels: [RowViewModelType], isMultiselectionEnabled: Bool) { -// self.rowViewModels = rowViewModels -// self.isMultiselectionEnabled = isMultiselectionEnabled -// } -// -// open func viewDidLoad() { -// initiallySelectedValues = selectedValues -// } -// -// open func setSelected(model: RowViewModelType, isSelected: Bool) { -// if !isMultiselectionEnabled { -// rowViewModels.enumerated().forEach { -// rowViewModels[$0.offset].isSelected = false -// } -// } -// -// if let index = rowViewModels.firstIndex(of: model) { -// rowViewModels[index].isSelected = isSelected -// } -// -// if !selectedValues.isEmpty { -// delegate?.filters(didSelect: selectedValues) -// } -// } -//} diff --git a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift b/TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift deleted file mode 100644 index 91af7939..00000000 --- a/TIEcommerce/Sources/Filters/ListFilters/ViewModels/DefaultFilterListSectionBuilder.swift +++ /dev/null @@ -1,49 +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 TableKit -import TITableKitUtils -import TIUIElements - -//open class DefaultFilterListSectionBuilder: FilterTableSectionBuilder { -// -// open func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.RowViewModel == CellType.CellData { -// let rows = viewModel.visibleValues.map { item in -// TableRow(item: item) -// .on(.select) { [weak viewModel] _ in -// viewModel?.setSelected(model: item, isSelected: !item.isSelected) -// } -// .on(.deselect) { [weak viewModel] _ in -// viewModel?.setSelected(model: item, isSelected: false) -// } -// } -// -// let separatedRows: [SeparatorRowBox] = rows.map { $0.separatorRowBox } -// let separator = SeparatorConfiguration(color: .gray) -// -// separatedRows.configureSeparators(first: separator, -// middle: separator, -// last: viewModel.isFinishWithSeparator ? separator : SeparatorConfiguration(color: .clear)) -// -// return .init(rows: separatedRows.rows) -// } -//} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift b/TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift deleted file mode 100644 index a5fd4672..00000000 --- a/TIEcommerce/Sources/Filters/ListFilters/Views/BaseCustomTableView.swift +++ /dev/null @@ -1,82 +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 TableKit -import TIUIElements -import TIUIKitCore -import UIKit - -open class BaseCustomTableView: BaseInitializableView, UIScrollViewDelegate { - - private var tableViewEdgeContraints: EdgeConstraints! - private var tableViewHeightConstraint: NSLayoutConstraint! - - private lazy var tableDirector = TableDirector(tableView: tableView, scrollDelegate: self) - - public let tableView = UITableView(frame: .zero, style: .grouped) - - public weak var scrollableViewDelegate: UIScrollViewDelegate? - - open override func addViews() { - super.addViews() - - addSubview(tableView) - } - - open override func configureLayout() { - super.configureLayout() - - tableView.translatesAutoresizingMaskIntoConstraints = false - - tableViewEdgeContraints = .init(leadingConstraint: tableView.leadingAnchor.constraint(equalTo: leadingAnchor), - trailingConstraint: tableView.trailingAnchor.constraint(equalTo: trailingAnchor), - topConstraint: tableView.topAnchor.constraint(equalTo: topAnchor), - bottomConstraint: tableView.topAnchor.constraint(equalTo: topAnchor)) - - tableViewHeightConstraint = tableView.heightAnchor.constraint(equalTo: heightAnchor) - - NSLayoutConstraint.activate([ - tableViewEdgeContraints.allConstraints, - [tableViewHeightConstraint] - ].flatMap { $0 }) - } - - open override func configureAppearance() { - super.configureAppearance() - - tableView.separatorStyle = .none - - [self, tableView].forEach { $0.backgroundColor = .white } - } - - open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { - scrollableViewDelegate?.scrollViewWillBeginDragging?(scrollView) - } - - open func scrollViewDidScroll(_ scrollView: UIScrollView) { - scrollableViewDelegate?.scrollViewDidScroll?(scrollView) - } - - open func updateTableView(_ section: TableSection) { - tableDirector.replace(withSection: section) - } -} diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FiltersPickerDelegate.swift b/TIEcommerce/Sources/Filters/Protocols/FiltersPickerDelegate.swift similarity index 100% rename from TIEcommerce/Sources/Filters/ListFilters/Protocols/FiltersPickerDelegate.swift rename to TIEcommerce/Sources/Filters/Protocols/FiltersPickerDelegate.swift diff --git a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift b/TISwiftUtils/Sources/Protocols/Selectable.swift similarity index 76% rename from TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift rename to TISwiftUtils/Sources/Protocols/Selectable.swift index 556260b0..946dd715 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/Protocols/FilterTableSectionBuilder.swift +++ b/TISwiftUtils/Sources/Protocols/Selectable.swift @@ -20,11 +20,19 @@ // THE SOFTWARE. // -import TableKit -import TIUIElements +public protocol Selectable: AnyObject { + var isSelected: Bool { get set } -//public protocol FilterTableSectionBuilder { -// associatedtype CellType: BaseSeparatorCell & ConfigurableCell -// -// func makeSection(with viewModel: ViewModel) -> TableSection where ViewModel.RowViewModel == CellType.CellData -//} + func setSelected() + func setDeselected() +} + +public extension Selectable { + func setSelected(_ isSelected: Bool) { + isSelected ? setSelected() : setDeselected() + } + + func setSelected() { isSelected = true } + + func setDeselected() { isSelected = false } +} From c25ff8c431476d0db551991189ff4122ed86c3bd Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Wed, 31 Aug 2022 18:53:40 +0300 Subject: [PATCH 14/21] fix: picker system --- .../DefaultFilterPropertyValue.swift | 0 .../Protocols/FilterPropertyValueRepresenter.swift | 0 .../BaseFilterViewModel.swift | 3 +++ .../DefaultFilterViewModel.swift | 0 .../Array+FilterPropertyValueRepresenter.swift | 0 .../Protocols/FilterViewModelProtocol.swift | 0 .../FiltersTableView/BaseFiltersTableView.swift | 2 +- .../FiltersTableView/DefaultFilterTableView.swift | 0 .../DefaultFilterTableViewCell.swift | 11 +++++++++-- .../FiltersTableViewCell/DefaultPickerView.swift | 2 +- .../DefaultFilterCollectionCell.swift | 0 .../UICollectionViewLayout+DefaultLayout.swift | 0 .../Models/FiltersLayoutConfiguration.swift | 0 .../Views/BaseFiltersCollectionView.swift | 0 .../Views/DefaultFiltersCollectionView.swift | 0 .../Filters/Protocols/FiltersPickerDelegate.swift | 2 +- 16 files changed, 15 insertions(+), 5 deletions(-) rename TIEcommerce/Sources/Filters/{TagsFilters/FiltersCollectionView/Models => FilterModels}/DefaultFilterPropertyValue.swift (100%) rename TIEcommerce/Sources/Filters/{TagsFilters/FiltersCollectionView => FilterModels}/Protocols/FilterPropertyValueRepresenter.swift (100%) rename TIEcommerce/Sources/Filters/{TagsFilters/FiltersCollectionView/ViewModels => FiltersViewModel}/BaseFilterViewModel.swift (96%) rename TIEcommerce/Sources/Filters/{TagsFilters/FiltersCollectionView/ViewModels => FiltersViewModel}/DefaultFilterViewModel.swift (100%) rename TIEcommerce/Sources/Filters/{TagsFilters/FiltersCollectionView => FiltersViewModel}/Helpers/Array+FilterPropertyValueRepresenter.swift (100%) rename TIEcommerce/Sources/Filters/{TagsFilters/FiltersCollectionView => FiltersViewModel}/Protocols/FilterViewModelProtocol.swift (100%) rename TIEcommerce/Sources/Filters/{ => FiltersViews}/ListFilters/FiltersTableView/BaseFiltersTableView.swift (99%) rename TIEcommerce/Sources/Filters/{ => FiltersViews}/ListFilters/FiltersTableView/DefaultFilterTableView.swift (100%) rename TIEcommerce/Sources/Filters/{ => FiltersViews}/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift (88%) rename TIEcommerce/Sources/Filters/{ => FiltersViews}/ListFilters/FiltersTableViewCell/DefaultPickerView.swift (98%) rename TIEcommerce/Sources/Filters/{TagsFilters/FiltersCollectionCell/Views => FiltersViews/TagsFilters/FiltersCollectionCell}/DefaultFilterCollectionCell.swift (100%) rename TIEcommerce/Sources/Filters/{ => FiltersViews}/TagsFilters/FiltersCollectionView/Helpers/UICollectionViewLayout+DefaultLayout.swift (100%) rename TIEcommerce/Sources/Filters/{ => FiltersViews}/TagsFilters/FiltersCollectionView/Models/FiltersLayoutConfiguration.swift (100%) rename TIEcommerce/Sources/Filters/{ => FiltersViews}/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift (100%) rename TIEcommerce/Sources/Filters/{ => FiltersViews}/TagsFilters/FiltersCollectionView/Views/DefaultFiltersCollectionView.swift (100%) diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/DefaultFilterPropertyValue.swift b/TIEcommerce/Sources/Filters/FilterModels/DefaultFilterPropertyValue.swift similarity index 100% rename from TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/DefaultFilterPropertyValue.swift rename to TIEcommerce/Sources/Filters/FilterModels/DefaultFilterPropertyValue.swift diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Protocols/FilterPropertyValueRepresenter.swift b/TIEcommerce/Sources/Filters/FilterModels/Protocols/FilterPropertyValueRepresenter.swift similarity index 100% rename from TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Protocols/FilterPropertyValueRepresenter.swift rename to TIEcommerce/Sources/Filters/FilterModels/Protocols/FilterPropertyValueRepresenter.swift diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/ViewModels/BaseFilterViewModel.swift b/TIEcommerce/Sources/Filters/FiltersViewModel/BaseFilterViewModel.swift similarity index 96% rename from TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/ViewModels/BaseFilterViewModel.swift rename to TIEcommerce/Sources/Filters/FiltersViewModel/BaseFilterViewModel.swift index a41f13fd..a56bc3a2 100644 --- a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/ViewModels/BaseFilterViewModel.swift +++ b/TIEcommerce/Sources/Filters/FiltersViewModel/BaseFilterViewModel.swift @@ -42,6 +42,7 @@ open class BaseFilterViewModel extension FilterCellStateAppearance { + private static let defaultContentInsets = UIEdgeInsets(top: 16, left: 8, bottom: 16, right: 8) + static var defaultSelectedRowAppearance: FilterCellStateAppearance { var selectionImage: UIImage? @@ -83,11 +85,16 @@ extension FilterCellStateAppearance { backgroundColor: .white, fontColor: .black, borderWidth: .zero, - cornerRadius: .zero, + contentInsets: defaultContentInsets, cornerRadius: .zero, selectionImage: selectionImage) } static var defaultNormalRowAppearance: FilterCellStateAppearance { - .init(borderColor: .clear, backgroundColor: .white, fontColor: .black, borderWidth: .zero, cornerRadius: .zero, selectionImage: nil) + .init(borderColor: .clear, + backgroundColor: .white, + fontColor: .black, + borderWidth: .zero, + contentInsets: defaultContentInsets, + cornerRadius: .zero) } } diff --git a/TIEcommerce/Sources/Filters/ListFilters/FiltersTableViewCell/DefaultPickerView.swift b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift similarity index 98% rename from TIEcommerce/Sources/Filters/ListFilters/FiltersTableViewCell/DefaultPickerView.swift rename to TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift index b18e9836..2410bdf8 100644 --- a/TIEcommerce/Sources/Filters/ListFilters/FiltersTableViewCell/DefaultPickerView.swift +++ b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift @@ -56,7 +56,7 @@ open class DefaultPickerView: BaseInitializableView { } open var defaultImageSize: CGFloat { - image?.size.height ?? 0 + 20 } open override func addViews() { diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Views/DefaultFilterCollectionCell.swift b/TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionCell/DefaultFilterCollectionCell.swift similarity index 100% rename from TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionCell/Views/DefaultFilterCollectionCell.swift rename to TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionCell/DefaultFilterCollectionCell.swift diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Helpers/UICollectionViewLayout+DefaultLayout.swift b/TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Helpers/UICollectionViewLayout+DefaultLayout.swift similarity index 100% rename from TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Helpers/UICollectionViewLayout+DefaultLayout.swift rename to TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Helpers/UICollectionViewLayout+DefaultLayout.swift diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/FiltersLayoutConfiguration.swift b/TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Models/FiltersLayoutConfiguration.swift similarity index 100% rename from TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Models/FiltersLayoutConfiguration.swift rename to TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Models/FiltersLayoutConfiguration.swift diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift b/TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift similarity index 100% rename from TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift rename to TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift diff --git a/TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/DefaultFiltersCollectionView.swift b/TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Views/DefaultFiltersCollectionView.swift similarity index 100% rename from TIEcommerce/Sources/Filters/TagsFilters/FiltersCollectionView/Views/DefaultFiltersCollectionView.swift rename to TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Views/DefaultFiltersCollectionView.swift diff --git a/TIEcommerce/Sources/Filters/Protocols/FiltersPickerDelegate.swift b/TIEcommerce/Sources/Filters/Protocols/FiltersPickerDelegate.swift index 29634087..af1a15ad 100644 --- a/TIEcommerce/Sources/Filters/Protocols/FiltersPickerDelegate.swift +++ b/TIEcommerce/Sources/Filters/Protocols/FiltersPickerDelegate.swift @@ -21,5 +21,5 @@ // public protocol FiltersPickerDelegate: AnyObject { - func filters(didSelect filters: [FilterPropertyValueRepresenter]) + func filters(didSelected filters: [FilterPropertyValueRepresenter]) } From 090b86563d7d20d1883d659f359fd65651143822 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Wed, 31 Aug 2022 19:01:10 +0300 Subject: [PATCH 15/21] fix: multiselection table setup --- .../ListFilters/FiltersTableView/BaseFiltersTableView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableView/BaseFiltersTableView.swift b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableView/BaseFiltersTableView.swift index e614cac7..16edba58 100644 --- a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableView/BaseFiltersTableView.swift +++ b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableView/BaseFiltersTableView.swift @@ -54,6 +54,8 @@ open class BaseFiltersTableView Date: Wed, 31 Aug 2022 19:09:48 +0300 Subject: [PATCH 16/21] docs: changelog update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8216cdc..f99da122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### 1.27.0 - **Add**: Tag like filter collection view +- **ADD**: List like filter table view ### 1.26.0 From 92f06c83a4438d28975c45a1394e186644d8b121 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Fri, 2 Sep 2022 11:51:37 +0300 Subject: [PATCH 17/21] fix: code review notes --- .../Models/FilterCellStateAppearance.swift | 7 ++-- .../DefaultFilterTableViewCell.swift | 32 +++++++++++------ .../DefaultPickerView.swift | 35 +++++++++++++++---- .../Sources/Protocols/Selectable.swift | 9 ++--- .../Sources/Wrappers/EdgeConstraints.swift | 11 ------ 5 files changed, 55 insertions(+), 39 deletions(-) diff --git a/TIEcommerce/Sources/Filters/Cells/Models/FilterCellStateAppearance.swift b/TIEcommerce/Sources/Filters/Cells/Models/FilterCellStateAppearance.swift index 74763640..ef80c2e1 100644 --- a/TIEcommerce/Sources/Filters/Cells/Models/FilterCellStateAppearance.swift +++ b/TIEcommerce/Sources/Filters/Cells/Models/FilterCellStateAppearance.swift @@ -20,6 +20,7 @@ // THE SOFTWARE. // +import TIUIKitCore import UIKit public struct FilterCellStateAppearance { @@ -32,7 +33,7 @@ public struct FilterCellStateAppearance { public let contentInsets: UIEdgeInsets public let cornerRadius: CGFloat - public let selectionImage: UIImage? + public let stateImages: UIControl.StateImages? public init(borderColor: UIColor, backgroundColor: UIColor, @@ -40,7 +41,7 @@ public struct FilterCellStateAppearance { borderWidth: CGFloat, contentInsets: UIEdgeInsets = .init(top: 4, left: 8, bottom: 4, right: 8), cornerRadius: CGFloat = 6, - selectionImage: UIImage? = nil) { + stateImages: UIControl.StateImages? = nil) { self.borderColor = borderColor self.backgroundColor = backgroundColor @@ -48,6 +49,6 @@ public struct FilterCellStateAppearance { self.borderWidth = borderWidth self.contentInsets = contentInsets self.cornerRadius = cornerRadius - self.selectionImage = selectionImage + self.stateImages = stateImages } } diff --git a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift index 960e9064..91dec1b0 100644 --- a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift +++ b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift @@ -61,7 +61,7 @@ open class DefaultFilterTableViewCell: ContainerTableViewCell open func updateAppearance(with appearance: FilterCellStateAppearance) { contentInsets = appearance.contentInsets wrappedView.textColor = appearance.fontColor - wrappedView.image = appearance.selectionImage + wrappedView.images backgroundColor = appearance.backgroundColor layer.borderColor = appearance.borderColor.cgColor @@ -72,13 +72,16 @@ open class DefaultFilterTableViewCell: ContainerTableViewCell extension FilterCellStateAppearance { - private static let defaultContentInsets = UIEdgeInsets(top: 16, left: 8, bottom: 16, right: 8) + @available(iOS 13, *) + private static let defaultStateImages: UIControl.StateImages = [.normal: nil, + .selected: UIImage(systemName: "checkmark")] + private static let defaultContentInsets = UIEdgeInsets(top: 16, left: 8, bottom: 16, right: 8) static var defaultSelectedRowAppearance: FilterCellStateAppearance { - var selectionImage: UIImage? + var stateImages: UIControl.StateImages? if #available(iOS 13, *) { - selectionImage = UIImage(systemName: "checkmark") + stateImages = defaultStateImages } return .init(borderColor: .clear, @@ -86,15 +89,22 @@ extension FilterCellStateAppearance { fontColor: .black, borderWidth: .zero, contentInsets: defaultContentInsets, cornerRadius: .zero, - selectionImage: selectionImage) + stateImages: stateImages) } static var defaultNormalRowAppearance: FilterCellStateAppearance { - .init(borderColor: .clear, - backgroundColor: .white, - fontColor: .black, - borderWidth: .zero, - contentInsets: defaultContentInsets, - cornerRadius: .zero) + var stateImages: UIControl.StateImages? + + if #available(iOS 13, *) { + stateImages = defaultStateImages + } + + return .init(borderColor: .clear, + backgroundColor: .white, + fontColor: .black, + borderWidth: .zero, + contentInsets: defaultContentInsets, + cornerRadius: .zero, + stateImages: stateImages) } } diff --git a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift index 2410bdf8..bf7a43b2 100644 --- a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift +++ b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift @@ -21,19 +21,34 @@ // import TIUIElements +import TIUIKitCore +import TISwiftUtils import UIKit -open class DefaultPickerView: BaseInitializableView { +open class DefaultPickerView: BaseInitializableView, Selectable { private let titleLabel = UILabel() private let selectionStateImageView = UIImageView() - open var image: UIImage? { - get { - selectionStateImageView.image - } - set { - selectionStateImageView.image = newValue + private var normalImage: UIImage? + private var selectedImage: UIImage? + + open var images: UIControl.StateImages? { + didSet { + guard let images = images else { return } + + for (state, image) in images { + switch state { + case .normal: + normalImage = image + case .highlighted: + selectionStateImageView.highlightedImage = image + case .selected: + selectedImage = image + default: + continue + } + } } } @@ -55,6 +70,12 @@ open class DefaultPickerView: BaseInitializableView { } } + open var isSelected: Bool = false { + didSet { + selectionStateImageView.image = isSelected ? selectedImage : normalImage + } + } + open var defaultImageSize: CGFloat { 20 } diff --git a/TISwiftUtils/Sources/Protocols/Selectable.swift b/TISwiftUtils/Sources/Protocols/Selectable.swift index 946dd715..f9dc585d 100644 --- a/TISwiftUtils/Sources/Protocols/Selectable.swift +++ b/TISwiftUtils/Sources/Protocols/Selectable.swift @@ -23,16 +23,11 @@ public protocol Selectable: AnyObject { var isSelected: Bool { get set } - func setSelected() - func setDeselected() + func setSelected(_ isSelected: Bool) } public extension Selectable { func setSelected(_ isSelected: Bool) { - isSelected ? setSelected() : setDeselected() + self.isSelected = isSelected } - - func setSelected() { isSelected = true } - - func setDeselected() { isSelected = false } } diff --git a/TIUIElements/Sources/Wrappers/EdgeConstraints.swift b/TIUIElements/Sources/Wrappers/EdgeConstraints.swift index 8afbc0ca..0274afd8 100644 --- a/TIUIElements/Sources/Wrappers/EdgeConstraints.swift +++ b/TIUIElements/Sources/Wrappers/EdgeConstraints.swift @@ -28,17 +28,6 @@ public struct EdgeConstraints { public let topConstraint: NSLayoutConstraint public let bottomConstraint: NSLayoutConstraint - public init(leadingConstraint: NSLayoutConstraint, - trailingConstraint: NSLayoutConstraint, - topConstraint: NSLayoutConstraint, - bottomConstraint: NSLayoutConstraint) { - - self.leadingConstraint = leadingConstraint - self.trailingConstraint = trailingConstraint - self.topConstraint = topConstraint - self.bottomConstraint = bottomConstraint - } - public var allConstraints: [NSLayoutConstraint] { [ leadingConstraint, From 36962696356f17e45bb80565fad97c651080e0d6 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Fri, 2 Sep 2022 12:01:51 +0300 Subject: [PATCH 18/21] fix: picker selection --- .../FiltersTableViewCell/DefaultFilterTableViewCell.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift index 91dec1b0..f487be9c 100644 --- a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift +++ b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift @@ -39,6 +39,7 @@ open class DefaultFilterTableViewCell: ContainerTableViewCell didSet { let appearance = isSelected ? selectedStateAppearance : normalStateAppearance updateAppearance(with: appearance) + wrappedView.setSelected(isSelected) } } @@ -61,7 +62,7 @@ open class DefaultFilterTableViewCell: ContainerTableViewCell open func updateAppearance(with appearance: FilterCellStateAppearance) { contentInsets = appearance.contentInsets wrappedView.textColor = appearance.fontColor - wrappedView.images + wrappedView.images = appearance.stateImages backgroundColor = appearance.backgroundColor layer.borderColor = appearance.borderColor.cgColor From 21a73a8f7cbba72acd0a18833b07b75879b7f1a1 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Mon, 5 Sep 2022 17:13:02 +0300 Subject: [PATCH 19/21] fix: code review notes --- .../FiltersTableView/BaseFiltersTableView.swift | 16 +++++----------- .../DefaultFilterTableViewCell.swift | 9 +++++---- .../Views/BaseFiltersCollectionView.swift | 6 +++--- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableView/BaseFiltersTableView.swift b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableView/BaseFiltersTableView.swift index 16edba58..84d28809 100644 --- a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableView/BaseFiltersTableView.swift +++ b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableView/BaseFiltersTableView.swift @@ -40,7 +40,7 @@ open class BaseFiltersTableView public typealias Snapshot = NSDiffableDataSourceSnapshot - public weak var viewModel: BaseFilterViewModel? + public let viewModel: BaseFilterViewModel public lazy var tableViewDataSource = createDataSource() @@ -90,7 +90,7 @@ open class BaseFiltersTableView.Change]) { + open func applyChanges(_ changes: [BaseFilterViewModel.Change]) { changes.forEach { change in guard let cell = cellForRow(at: change.indexPath) as? CellType else { return diff --git a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift index f487be9c..a6f38143 100644 --- a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift +++ b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift @@ -32,7 +32,7 @@ open class DefaultFilterTableViewCell: ContainerTableViewCell } open var normalStateAppearance: FilterCellStateAppearance { - .defaultNormalRowAppearance + .defaultRowAppearance } open override var isSelected: Bool { @@ -75,7 +75,7 @@ extension FilterCellStateAppearance { @available(iOS 13, *) private static let defaultStateImages: UIControl.StateImages = [.normal: nil, - .selected: UIImage(systemName: "checkmark")] + .selected: UIImage(systemName: "checkmark")] private static let defaultContentInsets = UIEdgeInsets(top: 16, left: 8, bottom: 16, right: 8) static var defaultSelectedRowAppearance: FilterCellStateAppearance { @@ -89,11 +89,12 @@ extension FilterCellStateAppearance { backgroundColor: .white, fontColor: .black, borderWidth: .zero, - contentInsets: defaultContentInsets, cornerRadius: .zero, + contentInsets: defaultContentInsets, + cornerRadius: .zero, stateImages: stateImages) } - static var defaultNormalRowAppearance: FilterCellStateAppearance { + static var defaultRowAppearance: FilterCellStateAppearance { var stateImages: UIControl.StateImages? if #available(iOS 13, *) { diff --git a/TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift b/TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift index 81073b0b..9fa9e155 100644 --- a/TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift +++ b/TIEcommerce/Sources/Filters/FiltersViews/TagsFilters/FiltersCollectionView/Views/BaseFiltersCollectionView.swift @@ -121,7 +121,7 @@ open class BaseFiltersCollectionView.Change]) { + open func applyChanges(_ changes: [BaseFilterViewModel.Change]) { changes.forEach { change in guard let cell = cellForItem(at: change.indexPath) as? CellType else { return From 698b79d10facb221bb2e2e38f922944c5aa5225f Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Mon, 5 Sep 2022 17:19:28 +0300 Subject: [PATCH 20/21] refactor: spaces in switch --- .../ListFilters/FiltersTableViewCell/DefaultPickerView.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift index bf7a43b2..12aefab3 100644 --- a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift +++ b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift @@ -41,10 +41,13 @@ open class DefaultPickerView: BaseInitializableView, Selectable { switch state { case .normal: normalImage = image + case .highlighted: selectionStateImageView.highlightedImage = image + case .selected: selectedImage = image + default: continue } From f3081861a0fe296f6297dd99eaeeb7661aabbc69 Mon Sep 17 00:00:00 2001 From: Nikita Semenov Date: Tue, 6 Sep 2022 15:42:12 +0300 Subject: [PATCH 21/21] fix: review notes --- .../DefaultFilterTableViewCell.swift | 2 +- .../DefaultPickerView.swift | 25 +++---------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift index a6f38143..759c9270 100644 --- a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift +++ b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultFilterTableViewCell.swift @@ -62,7 +62,7 @@ open class DefaultFilterTableViewCell: ContainerTableViewCell open func updateAppearance(with appearance: FilterCellStateAppearance) { contentInsets = appearance.contentInsets wrappedView.textColor = appearance.fontColor - wrappedView.images = appearance.stateImages + wrappedView.images = appearance.stateImages ?? [:] backgroundColor = appearance.backgroundColor layer.borderColor = appearance.borderColor.cgColor diff --git a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift index 12aefab3..a0bdff15 100644 --- a/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift +++ b/TIEcommerce/Sources/Filters/FiltersViews/ListFilters/FiltersTableViewCell/DefaultPickerView.swift @@ -30,27 +30,10 @@ open class DefaultPickerView: BaseInitializableView, Selectable { private let titleLabel = UILabel() private let selectionStateImageView = UIImageView() - private var normalImage: UIImage? - private var selectedImage: UIImage? - - open var images: UIControl.StateImages? { + open var images: UIControl.StateImages = [:] { didSet { - guard let images = images else { return } - - for (state, image) in images { - switch state { - case .normal: - normalImage = image - - case .highlighted: - selectionStateImageView.highlightedImage = image - - case .selected: - selectedImage = image - - default: - continue - } + if images.contains(where: { $0.key == .highlighted }) { + selectionStateImageView.highlightedImage = images[.highlighted] ?? nil } } } @@ -75,7 +58,7 @@ open class DefaultPickerView: BaseInitializableView, Selectable { open var isSelected: Bool = false { didSet { - selectionStateImageView.image = isSelected ? selectedImage : normalImage + selectionStateImageView.image = images[isSelected ? .selected : .normal] ?? nil } }