测试已测试 | ✗ |
Lang语言 | SwiftSwift |
许可证 | MIT |
发布最近发布 | 2017年7月 |
SwiftSwift 版本 | 3.0 |
SPM支持 SPM | ✗ |
由 John Volk 维护。
在实现 UITableView
UI 时,往往会出现包含许多易出错实现的 UITableViewDataSource
和 UITableViewDelegate
的 controller 对象。
例如
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case selectedItemSection:
return 1
case fooSection:
return self.showFoo ? 4 : 1
default:
let thingCount = self.thingCollections[section - 1].things.count
return thingCount == 0 ? 1 : thingCount
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch indexPath.section {
case self.selectedItemSection:
...
case fooSection:
switch indexPath.row {
case 0:
...
case 1:
...
case 2:
...
case 3:
...
default:
break
}
default:
let things = self.thingCollections[indexPath.section - 1].things
if things.count > 0 {
...
} else {
...
}
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let section = indexPath.section
if section != selectedItemSection || (section == selectedItemSection && self.selectedItem == nil) {
...
} else {
...
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
switch indexPath.section {
case selectedItemSection:
break
case fooSection:
break
default:
let things = self.thingCollections[indexPath.section - 1].things
if things.count > 0 {
self.performSegueWithIdentifier("showThings", sender: self)
}
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
创建 TableViewConfigurator
是为了消除这种类型的代码,用更声明性的方法来替换。
TableViewConfigurator 通过 CocoaPods 提供。要安装它,只需将以下行添加到您的 Podfile 中
pod "TableViewConfigurator"
TableViewConfigurator
基于 RowConfiguration
和 SectionConfiguration
的概念。在概念层次的底层是 RowConfiguration
。一个 RowConfiguration
允许您指定在您的 UITableView
中出现的单个行或行组。它目前有两种类型:ConstantRowConfiguration
和 ModelRowConfiguration
。
ConstantRowConfiguration
表示您的 UITableView
中的一个单一行。创建它的唯一方法是实现 ConfigurableTableViewCell
协议,该协议由 ConstantRowConfiguration
的构造函数通过泛型类型参数指定。
import UIKit
import TableViewConfigurator
class BasicCell: UITableViewCell, ConfigurableTableViewCell {
func configure() {
self.textLabel?.text = "Basic Cell"
}
}
let rowConfiguration = ConstantRowConfiguration
此时 rowConfiguration
已准备好使用,将在适当的时刻调用其 configure()
方法。但是,在使用前有几种不同的配置可以应用。
默认情况下,TableViewConfigurator
会为您的 cell 类生成一个与类名相同的重用标识符。如果这不是您想要的行为,您可以选择在您的 cell 类中覆盖 buildReuseIdentifier()
,或者在您的控制器中指定重用标识符。
let rowConfiguration = ConstantRowConfiguration
您可以根据您使用的尺寸方法指定单元格的身高或估计身高。
let rowConfiguration = ConstantRowConfiguration<BasicCell>().height(44.0)
let anotherConfiguration = ConstantRowConfiguration<BasicCell>().estimatedHeight(44.0)
您可以在控制器上下文中,在调用 cell 的 configure()
方法后,指定应在单元格上发生额外配置。
let rowConfiguration = ConstantRowConfiguration<BasicCell>()
.additionalConfig({ (cell) -> Void in
cell.accessoryType = self.someControllerFlag ? .DisclosureIndicator : .None
})
您可以在当 ConstantRowConfiguration
中的行被选中时,指定应在控制器上下文中调用的代码。
let rowConfiguration = ConstantRowConfiguration<BasicCell>()
.selectionHandler({ self.performSegueWithIdentifier("someSegue", sender: self) })
您可以指定一个闭包,用于确定单元格是否可以参与 UITableView
编辑工作流程。
您可以使用闭包处理从底层 UITableView
接收到的编辑事件。
最后,您还可以指定一个闭包,以指示何时应隐藏 ConstantRowConfiguration
中的行。
let rowConfiguration = ConstantRowConfiguration<BasicCell>()
.hideWhen({ () -> Bool in
return self.shouldHideRow
})
ModelRowConfiguration
表示由模型类型数组定义的一组行。它具有与 ConstantRowConfiguration
相同的所有配置选项,但是您定义的闭包回调将接受一个代表相关行模型的 model
参数和一个代表模型在当前模型序列中的位置的 index
参数。此外,它的构造函数需要两个泛型类型参数。第一个是一个 ModelConfigurableTableViewCell
的实现,第二个是一个符合 RowModel 协议的类。
它的构造函数还可以传入一个返回更新模型的函数。这对于动态 UI 非常有用。当使用 modelGenerator
时,您还可以选择性地指定模型生成是否应该优化。当您传递 true
时,生成器将仅在调用 TableViewConfigurator 本身的 changeSetAfterPerformingOperation()
或 reloadData()
时调用。在底层 UITableView
上调用 reloadData()
不会产生任何可见的变化。
class PersonCell: UITableViewCell, ModelConfigurableTableViewCell {
@IBOutlet var nameLabel: UILabel!
@IBOutlet var ageLabel: UILabel!
func configure(model: Person) {
self.nameLabel.text = "\(model.firstName) \(model.lastName)"
self.ageLabel.text = "Age \(model.age)"
}
}
let rowConfiguration = ModelRowConfiguration <PersonCell, Person>(models: self.people)
RowModel
协议允许 ModelRowConfiguration 准确比较配置的连续状态下,用于在表中动画变化。该协议要求一个名为 identityTag
的属性,以唯一标识任何给定的 RowModel
。默认情况下,每个模型都会根据其在当前模型序列中的索引获得相应的 identityTag
。对于仅改变模型在当前序列中可见性的情况,这通常是足够的,但如果本身序列发生修改,则将提供错误的结果。
ModelRowConfiguration
还添加了几个额外的“生成器”属性。
您可以将一个函数指定为返回模型行最新的高度。
您可以将一个函数指定为返回模型行最新的估计高度。
将 RowConfiguration
实例组合到 SectionConfiguration
中时,才能真正体现 TableViewConfigurator
的强大。您可以按任意顺序对 RowConfiguration
实例进行分组,TableViewConfigurator 将生成对于其支持的 UITableViewDataSource
和 UITableViewDelegate
部分的正确结果。
例如,假设你想创建一个由N个元素组成的UITableView
部分,并且这些元素被两个常数行所包围。通常情况下,这样做既烦琐又容易出错。使用TableViewConfigurator
则变得简单。
let people = [Person(firstName: "John", lastName: "Doe", age: 50),
Person(firstName: "Alex", lastName: "Great", age: 32),
Person(firstName: "Napoléon", lastName: "Bonaparte", age: 18)]
let section = SectionConfiguration(rowConfigurations:
[ConstantRowConfiguration<BasicCell>(),
ModelRowConfiguration<PersonCell, Person>(models: people),
ConstantRowConfiguration<BasicCell>()])
对于SectionConfiguration
,还有两个额外的配置选项可用。
你可以指定用作 sections 标题的字符串。
你可以指定用作 sections 页脚的字符串。
一旦你创建了你的RowConfiguration
和SectionConfiguration
实例,下一步是将它们组合到你的TableViewConfigurator
中,并在适当的地方从你的控制器中委托给它。TableViewConfigurator
实现了UITableViewDataSource
和UITableViewDelegate
,但它不太可能实现你可能需要的全部部分,因此最好只从你的控制器中在适当的地方进行委托。
override func viewDidLoad() {
super.viewDidLoad()
let basicSection = SectionConfiguration(rowConfiguration:
ConstantRowConfiguration<BasicCell>()
.height(44.0))
let peopleRows = ModelRowConfiguration<PersonCell, Person>(models: self.people)
.hideWhen({ (model) -> Bool in
return self.hidePeople
})
.height(44.0)
let peopleSection = SectionConfiguration(rowConfigurations:
[ConstantRowConfiguration<SwitchCell>()
.additionalConfig({ (cell) -> Void in
cell.hideLabel.text = "Hide People"
cell.hideSwitch.on = self.hidePeople
cell.switchChangedHandler = { (on) -> Void in
self.configurator.animateChangeSet(self.configurator.changeSetAfterPerformingOperation({ self.hidePeople = on }))
}
})
.height(44.0), peopleRows, ConstantRowConfiguration<BasicCell>().height(44.0)])
let disclosureSection = SectionConfiguration(rowConfiguration:
ConstantRowConfiguration<DisclosureCell>()
.selectionHandler({ () -> Bool in
self.performSegueWithIdentifier("showDetails", sender: self)
return true
})
.height(44.0))
self.configurator = TableViewConfigurator(tableView: tableView, sectionConfigurations:
[basicSection, peopleSection, disclosureSection])
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.configurator.numberOfSectionsInTableView(tableView)
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.configurator.tableView(tableView, titleForHeaderInSection: section)
}
func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return self.configurator.tableView(tableView, titleForFooterInSection: section)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.configurator.tableView(tableView, numberOfRowsInSection: section)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return self.configurator.tableView(tableView, cellForRowAtIndexPath: indexPath)
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return self.configurator.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.configurator.tableView(tableView, didSelectRowAtIndexPath: indexPath)
}
正如上面的例子所示,TableViewConfigurator
也支持UITableView的行插入和删除。
为了支持行的插入和删除,你需要适当地设置你的cells的.hideWhen()处理程序(或者从你的模型生成器中返回nil),然后调用.changeSetAfterPerformingOperation()。TableViewConfigurator会注意到在执行操作前后的可见性变化,并将这些变化以TableViewChangeSet元组的形式返回给你。你所要做的只是将这些更改传递给.animateChangeSet()或你的UITableView
,然后你的行/部分就会相应地进行动画处理。
TableViewConfigurator
还提供了indexPathsFor(rowConfiguration:)
方法,以便你可以访问RowConfiguration
的实际NSIndexPath
数组。这对于调用reloadRowsAtIndexPaths()
在UITableView
上(以强制你的细胞从它们自己的模型或常量配置重新加载)来说非常有用。
有时你可能想要刷新当前可见的单元格(或所有可见的单元格)的内容,而不强制单元格完全重新加载。例如,如果您的单元格中包含一个UITextField
,进行重新加载(这会销毁并替换现有的单元格)会导致文本框失去焦点。为了解决这个问题,TableViewConfigurator提供了refreshAllRowConfigurations()方法,可以从模型或常量配置非破坏性地刷新任何可见的单元格。当然,当屏幕外的单元格变为可见时,它们会由TableView查询其代理以进行更新。
John Volk,[email protected]
TableViewConfigurator在MIT许可下可用。有关更多信息,请参阅LICENSE文件。