复合数据源对象和功能方法要素
一旦我(好的,实际上是队友)面临着一项任务, UICollectionView
另一个类型的单元格的UICollectionView
添加一个单元格。 此外,仅在特殊情况下才显示该额外的单元格,这种情况发生在外部并且不直接依赖于UICollectionView
。 实施的解决方案催生了需要的if
– else
语句(如果内存UICollectionViewDelegate
话)在UICollectionViewDataSource
和UICollectionViewDelegate
方法中,并且这些语句在生产代码中“安全地”存在了UICollectionViewDataSource
UICollectionViewDelegate
。
在任务中,无需考虑更优雅的解决方案,就可以浪费时间和精力。 但是,我一直牢记这个故事,并沉思着一个可以由任意数量的其他数据源组成的数据源实现。 一个显然必须被概括,适合任何数量的原始数据源(包括零和一),并且必须与特定类型无关。 事实证明,它不仅可行,而且也不太复杂(尽管使代码看起来更漂亮有点棘手)。
我想使用UITableView
的示例来分享我的先驱。 如果需要,必须容易实现类似的UICollectionView
代码。
原始文章是用俄语撰写的,并在此处发表。
“观念永远比客观化更重要”
这是 Alan Moore ( 守望者 , V代表仇杀 联盟,非凡绅士联盟 )的报价(perharps incurate),但是作为程序员,我们真的很在意特定的实现,对吧?
我方法的主要概念是存储UITableViewDateSource
对象的数组,返回其节的总数,并可以确定哪个原始数据源负责处理调用。
UITableViewDataSource
协议已经具有获取节数,行数等所有必要的方法,但是不幸的是,由于特定的UITableView
实例作为方法参数,我发现将它们用于我的目的非常尴尬。 这就是为什么我最后声明自己的协议,该协议扩展了UITableViewDataSource
的一些额外要求:
协议ComposableTableViewDataSource:UITableViewDataSource {
var numberOfSections:Int {get}
func numberOfRows(用于Int)-> Int
}
对于组合数据源,它成为符合UITableViewDataSource
的简单类,并且可以使用任意数量的ComposableTableViewDataSource
对象进行初始化:
最后一课ComposedTableViewDataSource:NSObject {
私有let dataSources:[ComposableTableViewDataSource]
init(dataSources:ComposableTableViewDataSource ...){
self.dataSources =数据源
super.init()
}
私人替代init(){
fatalError(“ ComposedTableViewDataSource:必须使用带有参数的初始化程序。”)
}
}
扩展ComposedTableViewDataSource:UITableViewDataSource {}
到那时,我要做的就是实现所有UITableViewDataSource
方法,并引用其组件的适当方法。
“这是正确的选择。 我的决定”
这些词语属于 俄罗斯联邦 第一 任总统 鲍里斯·埃尔辛 ( Boris Eltsin) , 与以下文本无关。 我只是喜欢它。
充分利用Swift的功能似乎是正确的选择,而且事实证明它非常方便。
首先,让我们实现返回许多部分的方法-这很简单。 如上所述,我们只需要所有组件部分的总数即可:
func numberOfSections(在tableView中:UITableView)-> Int {
//如果未实现,则默认值为“ 1”。
返回dataSources.reduce(0){
$ 0 +($ 1.numberOfSections?(in:tableView)?1)
}
}
(我不想解释语法(尽管没有习惯就不总是很清楚)或标准方法的含义。互联网上充斥着不少不错的介绍性文章。我也可以推荐一本非常好的书。)
在使自己熟悉所有UITableViewDataSource
方法之后,可以注意到每个方法都接受两个参数:一个表视图实例引用和多个节或相应的IndexPath
对象。 让我们介绍一些在实现其余协议方法时有用的助手。
首先,所有任务都可以带给带有两个参数的泛型函数:具体的ComposableTableViewDataSource
和行号或IndexPath
。 为了方便和简洁起见,让我们为此函数类型声明类型别名。 另外,我建议声明段号的类型别名:
专用类型别名SectionNumber = Int
私有类型别名AdducedSectionTask =(_ _ composableDataSource:ComposableTableViewDataSource,_ sectionNumber:SectionNumber)-> T
私有类型别名AdducedIndexPathTask =(_ _ composableDataSource:ComposableTableViewDataSource,_ indexPath:IndexPath)-> T
(所选名称将在后面说明。)
其次,让我们实现一个简单的方法,该方法将获取ComposedTableViewDataSource
节号并返回相应的ComposableTableViewDataSource
及其节号:
私有函数分解(section:SectionNumber)->(dataSource:ComposableTableViewDataSource,decomposedSection:SectionNumber){
var section = section
var dataSourceIndex = 0
用于dataSources.enumerated()中的(index,dataSource){
让diff = section-dataSource.numberOfSections
dataSourceIndex =索引
如果diff <0 {break} else {section = diff}
}
返回(dataSources [dataSourceIndex]部分)
}
也许,如果让它比我做的要透彻一点,则实现会更优雅,直线也更少。 例如,我的同事立即建议实施二进制搜索(最初具有段号索引-简单的整数数组)。 甚至花费一些时间在段号对应表的存储上形成和存储资源-这将使我们得到O(1)的结果,而不是一直使用O(n)或O(logn)复杂度的方法。 但是我决定采纳伟大的唐纳德·克努斯(Donald Knuth),在没有明显的必要性和适当的衡量标准的情况下,不要过早优化任何东西。 毕竟,本文是关于另一回事。
最后,我们需要采取在AdducedSectionTask
或AdducedIndexPathTask
上面声明的方法,并将其重定向到适当的ComposedTableViewDataSource
:
private func adduce (__ section:SectionNumber,
_任务:AdducedSectionTask )-> T {
让(数据源,
decomposedSection)=分解(section:section)
返回任务(dataSource,decomposedSection)
}
私有函数adduce (__ PathPath:IndexPath,
_任务:AdducedIndexPathTask )-> T {
让(数据源,
decomposedSection)=分解(section:indexPath.section)
返回任务(数据源,
IndexPath(row:indexPath.row,
部分:decomposedSection))
}
这次,我将尝试解释所选的名称:它们代表功能命名方式。 也就是说,名称的含义不大,但听起来确实令人印象深刻。
但是开玩笑吧!
后两种方法几乎与双胞胎相似。 但是考虑了一段时间之后,我放弃了摆脱看似重复的代码的尝试,因为这样做带来的不便多于帮助。 我们最终会遇到一个问题,即将泛型类型转换为节号,反之亦然,必须将其作为附加参数传递或推导到位。 而且,重用该代码的机会是零。
所有这些准备工作和助手在协议方法实现方面为我们提供了难以置信的优势。 这里配置表视图方法:
func tableView(_ tableView:UITableView,
titleForHeaderInSection部分:Int)->字符串? {
return adduce(section){
$ 0.tableView?(tableView,titleForHeaderInSection:$ 1)
}
}
func tableView(_ tableView:UITableView,
titleForFooterInSection部分:Int)->字符串? {
return adduce(section){
$ 0.tableView?(tableView,titleForFooterInSection:$ 1)
}
}
func tableView(_ tableView:UITableView,
numberOfRowsInSection部分:Int)-> Int {
return adduce(section){
$ 0.tableView(tableView,numberOfRowsInSection:$ 1)
}
}
func tableView(_ tableView:UITableView,
cellForRowAt indexPath:IndexPath)-> UITableViewCell {
返回adduce(indexPath){
$ 0.tableView(tableView,cellForRowAt:$ 1)
}
}
插入和删除行:
func tableView(_ tableView:UITableView,
提交editingStyle:UITableViewCell.EditingStyle,
forRowAt indexPath:IndexPath){
返回adduce(indexPath){
$ 0.tableView?(tableView,提交:editingStyle,forRowAt:$ 1)
}
}
func tableView(_ tableView:UITableView,
canEditRowAt indexPath:IndexPath)-> Bool {
返回adduce(indexPath){
//如果未实现,则默认为“ true”。
$ 0.tableView?(tableView,canEditRowAt:$ 1)? 真正
}
}
可以类似的方式实现对节索引标题的支持。 在这种情况下,必须操作标题索引而不是节号。 最有可能的是,向ComposableTableViewDataSource
协议添加一个附加属性会很有用。 我把它放在本文的范围之外。
“今天不可能,明天可能”
这句话属于 航天理论先驱 科学家 康斯坦丁·齐奥尔科夫斯基 ( Konstantin Tsiolkovsky) 。
首先,我的方法不支持行移动。 最初的计划包括将其移动到可组合的数据源中,但是不幸的是,它不能由UITableViewDataSource
方法单独实现。 在这些方法中,可以确定允许行继续移动并处理结果。 但是事件本身应该在UITableViewDelegate
协议方法实现中进行处理。
其次,更重要的是,对于动态表视图,必须考虑更新表示形式。 我认为,可以通过声明ComposableTableViewDataSource
委托的协议来实现此目标,该协议应该由ComposedTableViewDataSource
实现,并且在更改原始数据源之一时具有回调。
这给我们留下了两个未解决的问题:如何确定在ComposedTableViewDataSource
内部更改了哪个ComposableTableViewDataSource
,以及如何精确地进行了更改。 顺便说一句,后者是另一个不平凡的问题,但是,它有几种解决方案(例如,这个问题)。
最后,当然,我们将需要ComposedTableViewDataSource
委托协议,该协议必须在更改组合数据源时调用,并由客户端代码(例如,视图控制器或视图模型)实现。
希望随着时间的流逝,我会更深入地探讨这个问题,并在本文的下一部分中进行介绍。 现在,希望您对我的实验感到好奇!
聚苯乙烯
前几天,我不得不深入介绍部分中提到的代码。 任务很简单:交换不同类型的单元格。 简而言之,我不得不忍受一段时间来尝试克服不时提出的Index out of bounds
错误。 使用所描述的方法,我可以在作为参数传递给组合数据源初始化程序的数组中交换两个原始数据源。
链接:
- 带有完整代码的游乐场和示例
- 我的推特