该框架旨在简化 UITableView
数据源和单元格的设置和配置。 它允许对 UITableViewDataSource
和 (可选的) UITableViewDelegate
进行类型安全设置。 DataSource
还提供开箱即用的差异比较以及动画删除、插入、移动和更改功能。
随附了一个示例应用程序,演示了 DataSource 的功能。 该示例演示了各种用例,从简单的字符串列表到更复杂的用例,例如设置动态表单。
创建一个带有 CellDescriptor
的 DataSource
,该描述符描述了如何使用数据模型 (Example
) 配置 UITableViewCell
(在本例中为 TitleCell
)。 此外,我们还为 didSelect
添加了一个处理程序,用于处理 UITableViewDelegate
的 didSelectRowAtIndexPath
方法。
let dataSource: DataSource = {
DataSource(
cellDescriptors: [
CellDescriptor<Example, TitleCell>()
.configure { (example, cell, indexPath) in
cell.textLabel?.text = example.title
cell.accessoryType = .disclosureIndicator
}
.didSelect { (example, indexPath) in
self.performSegue(withIdentifier: example.segue, sender: nil)
return .deselect
}
])
}()
)
接下来,将您的 dataSource
设置为 UITableView
的 dataSource
和 delegate
。
tableView.dataSource = dataSource
tableView.delegate = dataSource
接下来,创建并设置模型。 不要忘记调用 reloadData
。
dataSource.sections = [
Section(items: [
Example(title: "Random Persons", segue: "showRandomPersons"),
Example(title: "Form", segue: "showForm"),
Example(title: "Lazy Rows", segue: "showLazyRows"),
Example(title: "Diff & Update", segue: "showDiff"),
])
]
dataSource.reloadData(tableView, animated: false)
DataSource
还可以用于配置节标题和节尾。 与 CellDescriptors
类似,您可以定义一个或多个 SectionDescriptors
。
let dataSource: DataSource = {
DataSource(
cellDescriptors: [
CellDescriptor()
.configure { (person, cell, indexPath) in
cell.configure(person: person)
}
],
sectionDescriptors: [
SectionDescriptor<String>()
.header { (title, _) in
.title(title)
}
])
}()
节标题和节尾可以具有自定义视图 (.view(...)
) 或简单的标题 (.title(...)
)。 还支持委托方法,例如 heightForHeaderInSection
(headerHeight
)。
如果您的数据模型实现了 Diffable
协议,则支持两组数据之间的差异比较和动画更改。
public protocol Diffable {
var diffIdentifier: String { get }
func isEqualToDiffable(_ other: Diffable?) -> Bool
}
diffIdentifier
是一个 String
标识符,用于描述两个模型的身份是否不同。 可以将其视为数据库中的主键。 不同的 diffIdentifiers
将导致动画插入、删除或移动更改。 此外,即使 diffIdentifier
相同,也可以使用 isEqualToDiffable
来描述模型的数据或内容是否已更改。 例如,如果在数据库中更改了一个人的姓名,则该人的主键通常保持不变。 在这种情况下,通常不希望进行插入、删除或移动,而是希望对表格中相应的行进行(可能带动画的)更新。
差异比较通过两个示例进行演示
RandomPersonsViewController
在两个节中创建一组随机人员,并为数据集之间的更改添加动画。
private func randomData() -> [SectionType] {
let count = Int.random(5, 15)
let persons = (0 ..< count).map { _ in Person.random() }.sorted()
let letters = Set(["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"])
let firstGroup = persons.filter {
$0.lastNameStartsWith(letters: letters)
}
let secondGroup = persons.filter {
!$0.lastNameStartsWith(letters: letters)
}
return [
Section("A - L", items: firstGroup),
Section("M - Z", items: secondGroup),
]
}
@IBAction func refresh(_ sender: Any) {
dataSource.sections = randomData()
dataSource.reloadData(tableView, animated: true)
}
DiffViewController
创建数字行,其中 diffIdentifier
是数字本身,而内容是该数字的英文或德文名称。 这显示了如何完成动画行更改。
struct DiffItem {
let value: Int
let text: String
let diffIdentifier: String
init(_ value: Int, text: String) {
self.value = value
self.text = text
self.diffIdentifier = String(value)
}
}
extension DiffItem: Diffable {
public func isEqualToDiffable(_ other: Diffable?) -> Bool {
guard let other = other as? DiffItem else { return false }
return self.text == other.text
}
}
请参阅示例以获取完整代码。
CellDescriptor
和 SectionDescriptor
都提供了一个 isHidden
闭包,允许您根据任何自定义条件简单地隐藏和显示行。
FormViewController
示例使用此方法仅在名字不为空时才显示姓氏字段,并在启用开关时显示“附加字段”部分
lazy var dataSource: DataSource = {
DataSource(
cellDescriptors: [
TextFieldCell.descriptor
.isHidden { (field, indexPath) in
if field.id == self.lastNameField.id {
return self.firstNameField.text?.isEmpty ?? true
} else {
return false
}
},
SwitchCell.descriptor,
TitleCell.descriptor,
],
sectionDescriptors: [
SectionDescriptor<Void>("section-name")
.headerHeight { .zero },
SectionDescriptor<Void>("section-additional")
.header {
.title("Additional Fields")
}
.isHidden {
!self.switchField.isOn
}
])
}()
每当调用 dataSource.reloadData(...)
时,都会评估 isHidden
闭包。
DataSource
提供了一种方便的方法,可以使用闭包以类型安全且简单的方式处理所有 UITableViewDelegate
方法。 在大多数情况下,您在 CellDescriptor
或 SectionDescriptor
上定义这些闭包。 但是,有时这会导致代码重复,例如,如果您有不同的单元格,但为选择执行的代码是相同的。 在这些情况下,您可以在 DataSource
本身设置委托闭包
dataSource.didSelect = { (row, indexPath) in
print("selected")
return .deselect
}
如果未在 CellDescriptor
(或 SectionDescriptor
) 上定义特定委托方法的闭包,则这些闭包将用作回退。
此外,您可以设置一个回退 UITableViewDelegate
和 UITableViewDataSource
,如果未设置 CellDescriptor
或 SectionDescriptor
上的匹配闭包,则再次使用它们。
dataSource.fallbackDelegate = self
dataSource.fallbackDataSource = self
使用这些回退机制,您可以选择要在特定用例中使用 DataSource
的哪些部分。 例如,您可以使用它来设置和配置所有单元格,在数据集之间制作动画更改,但保留现有的 UITableViewDelegate
代码。
fallbackDelegate
可用于实现不属于 DataSource
的方法,例如 UIScrollViewDelegate
方法。 您应格外小心,需要在设置表视图委托之前设置回退委托,否则 UIKit
永远不会调用某些委托方法。
// Always set the fallback before setting the table view delegate
dataSource.fallbackDelegate = self
tableView.dataSource = dataSource
tableView.delegate = dataSource
可以从自定义 bundle 注册单元格。 您可以在单元格描述符中指定应从哪个 bundle 加载单元格。 该 bundle 默认为主 bundle。
let descriptor = CellDescriptor(bundle: customBundle)
如果在单独的 xib(在 storyboard 中 tableView 定义之外)或完全在代码中定义单元格类型,则需要将单元格注册到要使用的 tableView。 您可以手动将单元格注册到 tableView(请参阅 UITableView 文档),或者让 DataSource 通过遵循 AutoRegisterCell
协议为您执行此操作。
当前 Swift 兼容性细分
Swift 版本 | 框架版本 |
---|---|
5.1 | 8.x |
5.0 | 7.x |
4.2 | 6.x |
4.1 | 5.x |
3.x | 3.x, 4.x |
将以下依赖项添加到您的 Package.swift
文件
.package(url: "https://github.com/allaboutapps/DataSource.git", from: "8.0.0")
将以下行添加到您的 Cartfile。
github "allaboutapps/DataSource", ~> 8.0
然后运行 carthage update
。
对于 DataSource,请在您的 Podfile 中使用以下条目
pod 'MBDataSource'
然后运行 pod install
。
在您想要使用 DataSource 的任何文件中,不要忘记使用 import DataSource
导入框架。
只需将 DataSource
文件夹中的 .swift
文件拖放到您的项目中即可。
请通过 matthias.buchetics.com 与我联系,或在 Twitter 上关注我。