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