UICollectionView
with a certain cell type, and doing this only in a special case that is processed “above” and does not depend on UICollectionView
directly. This task gave rise to, if my memory serves me, a couple of ugly if
- else
blocks inside the UICollectionViewDataSource
and UICollectionViewDelegate
, which safely settled into the “production” code and probably won't get anywhere from there.UITableView
. If desired, to write a similar code for a UICollectionView
should be no difficulty.UITableViewDataSource
objects, return their total number of sections, and be able to determine which of the original “datasource” objects to redirect this call to when accessing a section.UITableViewDataSource
protocol already has the necessary methods for obtaining the number of sections, lines, etc., but, unfortunately, in this case I found it extremely inconvenient to use them because of the need to pass a link to a specific instance of UITableView
as one of the arguments. So I decided to extend the standard UITableViewDataSource
protocol with a couple of extra simple members: protocol ComposableTableViewDataSource: UITableViewDataSource { var numberOfSections: Int { get } func numberOfRows(for section: Int) -> Int }
UITableViewDataSource
requirements and is initialized with just one argument — a set of concrete instances of the ComposableTableViewDataSource
: final class ComposedTableViewDataSource: NSObject, UITableViewDataSource { private let dataSources: [ComposableTableViewDataSource] init(dataSources: ComposableTableViewDataSource...) { self.dataSources = dataSources super.init() } private override init() { fatalError("ComposedTableViewDataSource: Initializer with parameters must be used.") } }
UITableViewDataSource
protocol so that they refer to the methods of the corresponding components. func numberOfSections(in tableView: UITableView) -> Int { // Default value if not implemented is "1". return dataSources.reduce(0) { $0 + ($1.numberOfSections?(in: tableView) ?? 1) } }
UITableViewDataSource
methods, you notice that they take as arguments only the reference to the table and the value of either the section number or the corresponding IndexPath
string. We will write several helpers who will be useful to us when implementing all the other protocol methods.ComposableTableViewDataSource
and the value of the section number or IndexPath
. For convenience and brevity, we assign pseudonyms to the types of these functions. Plus, for additional readability, I propose to declare an alias for the section number: private typealias SectionNumber = Int private typealias AdducedSectionTask<T> = (_ composableDataSource: ComposableTableViewDataSource, _ sectionNumber: SectionNumber) -> T private typealias AdducedIndexPathTask<T> = (_ composableDataSource: ComposableTableViewDataSource, _ indexPath: IndexPath) -> T
ComposedTableViewDataSource
section ComposedTableViewDataSource
determines the specific ComposableTableViewDataSource
and the corresponding section number: private func decompose(section: SectionNumber) -> (dataSource: ComposableTableViewDataSource, decomposedSection: SectionNumber) { var section = section var dataSourceIndex = 0 for (index, dataSource) in dataSources.enumerated() { let diff = section - dataSource.numberOfSections dataSourceIndex = index if diff < 0 { break } else { section = diff } } return (dataSources[dataSourceIndex], section) }
AdducedSectionTask
and AdducedIndexPathTask
indicated above and “redirect” them to specific ComposedTableViewDataSource
instances: private func adduce<T>(_ section: SectionNumber, _ task: AdducedSectionTask<T>) -> T { let (dataSource, decomposedSection) = decompose(section: section) return task(dataSource, decomposedSection) } private func adduce<T>(_ indexPath: IndexPath, _ task: AdducedIndexPathTask<T>) -> T { let (dataSource, decomposedSection) = decompose(section: indexPath.section) return task(dataSource, IndexPath(row: indexPath.row, section: decomposedSection)) }
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return adduce(section) { $0.tableView?(tableView, titleForHeaderInSection: $1) } } func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { return adduce(section) { $0.tableView?(tableView, titleForFooterInSection: $1) } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return adduce(section) { $0.tableView(tableView, numberOfRowsInSection: $1) } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return adduce(indexPath) { $0.tableView(tableView, cellForRowAt: $1) } }
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { return adduce(indexPath) { $0.tableView?(tableView, commit: editingStyle, forRowAt: $1) } } func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { // Default if not implemented is "true". return adduce(indexPath) { $0.tableView?(tableView, canEditRowAt: $1) ?? true } }
ComposableTableViewDataSource
protocol. I left this part outside the material.UITableViewDataSource
. The methods of this protocol determine whether a specific string can be “dragged” and receive a callback when the drag is completed. And the processing of the event itself is implied inside the UITableViewDelegate
methods.ComposableTableViewDataSource
delegate protocol, whose methods will be implemented by ComposedTableViewDataSource
and receive a signal that the original “datasource” has received an update. Two questions remain open: how inside the ComposedTableViewDataSource
reliably determine which ComposableTableViewDataSource
has changed and how it is a separate and not the most trivial task, but having a number of solutions (for example, such ). And, of course, you will need the ComposedTableViewDataSource
delegate ComposedTableViewDataSource
, whose methods will be called when the composite “datasource” is updated and implemented by the client type (for example, a controller or a view-model ).Index out of bounds
constantly appearing in different places. When using the described approach, it would only be necessary to swap two “datasource” objects in the array passed in as an initializer argument.Source: https://habr.com/ru/post/442138/
All Articles