DataSource

Swift 5 Carthage compatible CocoaPods compatible

该框架旨在简化 UITableView 数据源和单元格的设置和配置。 它允许对 UITableViewDataSource 和 (可选的) UITableViewDelegate 进行类型安全设置。 DataSource 还提供开箱即用的差异比较以及动画删除、插入、移动和更改功能。

用法

随附了一个示例应用程序,演示了 DataSource 的功能。 该示例演示了各种用例,从简单的字符串列表到更复杂的用例,例如设置动态表单。

入门

创建一个带有 CellDescriptorDataSource,该描述符描述了如何使用数据模型 (Example) 配置 UITableViewCell (在本例中为 TitleCell)。 此外,我们还为 didSelect 添加了一个处理程序,用于处理 UITableViewDelegatedidSelectRowAtIndexPath 方法。

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 设置为 UITableViewdataSourcedelegate

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)

节 (Sections)

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)。

差异比较 (Diffing)

如果您的数据模型实现了 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
    }
}

请参阅示例以获取完整代码。

隐藏行和节 (Hiding Rows and Sections)

CellDescriptorSectionDescriptor 都提供了一个 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 闭包。

委托和回退 (Delegates and Fallbacks)

DataSource 提供了一种方便的方法,可以使用闭包以类型安全且简单的方式处理所有 UITableViewDelegate 方法。 在大多数情况下,您在 CellDescriptorSectionDescriptor 上定义这些闭包。 但是,有时这会导致代码重复,例如,如果您有不同的单元格,但为选择执行的代码是相同的。 在这些情况下,您可以在 DataSource 本身设置委托闭包

dataSource.didSelect = { (row, indexPath) in
    print("selected")
    return .deselect
}

如果未在 CellDescriptor (或 SectionDescriptor) 上定义特定委托方法的闭包,则这些闭包将用作回退。

此外,您可以设置一个回退 UITableViewDelegateUITableViewDataSource,如果未设置 CellDescriptorSectionDescriptor 上的匹配闭包,则再次使用它们。

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 (Custom bundles)

可以从自定义 bundle 注册单元格。 您可以在单元格描述符中指定应从哪个 bundle 加载单元格。 该 bundle 默认为主 bundle。

let descriptor = CellDescriptor(bundle: customBundle)

单元格注册 (Cell Registration)

如果在单独的 xib(在 storyboard 中 tableView 定义之外)或完全在代码中定义单元格类型,则需要将单元格注册到要使用的 tableView。 您可以手动将单元格注册到 tableView(请参阅 UITableView 文档),或者让 DataSource 通过遵循 AutoRegisterCell 协议为您执行此操作。

版本兼容性 (Version Compatibility)

当前 Swift 兼容性细分

Swift 版本 框架版本
5.1 8.x
5.0 7.x
4.2 6.x
4.1 5.x
3.x 3.x, 4.x

安装 (Installation)

Swift Package Manager (推荐)

将以下依赖项添加到您的 Package.swift 文件

.package(url: "https://github.com/allaboutapps/DataSource.git", from: "8.0.0")

Carthage

将以下行添加到您的 Cartfile

github "allaboutapps/DataSource", ~> 8.0

然后运行 carthage update

CocoaPods

对于 DataSource,请在您的 Podfile 中使用以下条目

pod 'MBDataSource'

然后运行 pod install

在您想要使用 DataSource 的任何文件中,不要忘记使用 import DataSource 导入框架。

手动 (Manually)

只需将 DataSource 文件夹中的 .swift 文件拖放到您的项目中即可。

贡献 (Contributing)

联系 (Contact)

请通过 matthias.buchetics.com 与我联系,或在 Twitter 上关注我。