DataSourceController 框架
DataSourceController 是一个框架,提供了 UITableViewDataSource
和 UICollectionViewDataSource
协议的实现,并且通过 Section
实例提供了更声明性的 API。这使得我们能够从视图控制器中删除一些模板代码,从而使得代码库更加整洁。
需求
版本 | 需求 |
---|---|
1.0.0 | Xcode 10.0+ Swift 4.2+ iOS 10.0+ |
该框架使用 Swift 5.0 编写,但没有任何针对该 Swift 版本特定的代码。因此,它应该与使用 Swift 4.2 的项目兼容。
安装
您可以通过依赖管理器安装该框架。
使用 CocoaPods
要使用 CocoaPods,请首先确保您已安装它并按照 cocoapods.org 上的说明更新到最新版本。然后,您应该完成以下步骤:
- 将 DataSourceController 添加到您的
Podfile
中
pod 'DataSourceController', '~>1.2.0'
- 通过在命令行中执行以下命令,更新您的 pod 源并安装新 pod
$ pod install --repo-update
使用 Carthage
要使用 Carthage,首先请确保您已安装它并按照其仓库中的说明将其更新到最新版本。
- 将 DataSourceController 添加到您的
Cartfile
github "peredaniel/DataSourceController" ~> 1.2.0
- 运行 Carthage 以安装新框架
$ carthage update
使用 Swift Package Manager
使用 Swift Package Manager 安装时,请将以下内容添加到您的 Package.swift
文件中的 dependencies
部分
.package(url: "https://github.com/peredaniel/DataSourceController.git", .upToNextMinor(from: "1.2.0")),
开始使用
通过上述任何一种方法安装框架后,我们可以在任何 Swift 文件的 "header" 部分添加以下行以导入模块
import DataSourceController
第一步是实现我们的数据控制器。一个 "数据控制器" 是符合 CellDataController
协议的对象
protocol CellDataController {
var reuseIdentifier: String { get }
static func populate(with model: Any) -> CellDataController
}
数据控制器负责从模型对象获取数据(如果有必要),将其转换为可以由 UITableViewCell
或 UICollectionViewCell
在屏幕上显示的数据结构。注意,数据控制器与一个标识符相关联,而不是与一个类相关联。特别是,这意味着您可以将同一个控制器用于具有相同重用标识符的不同细胞类。
数据控制器的简单示例如下
struct BasicCellDataController: CellDataController {
let reuseIdentifier = "basicCell"
let titleText: String
static func populate(with model: Any) -> CellDataController {
guard let model = model as? String else {
return BasicCellDataController(titleText: "Incorrect data type")
}
return BasicCellDataController(titleText: model)
}
}
这是我们能够实现的最基本的控制器:它以 String
实例作为模型对象,仅仅在需要时存储它。当然,如果我们认为一个基本单元格只能显示标题的单元格,那么单元格的实际字体、对齐方式、背景或其他任何特定于 UI 的装饰器是单元格特定的,但所有这些背后的数据模型是相同的:一个 String
。因此,使用相同的数据控制器是有意义的,这也说明了为什么将控制器与标识符相关联,而不是与类本身相关联。
接下来的一步是启用单元格从数据控制器检索数据。为此,我们必须使我们的单元格类符合 CellView
协议
public protocol CellView {
func configure(with dataController: CellDataController)
}
按照上一个示例,我们可以扩展 UITableViewCell
以符合 CellView
协议,如下所示
extension UITableViewCell: CellView {
public func configure(with dataController: CellDataController) {
guard let basicController = dataController as? BasicCellDataController else { return }
textLabel?.text = basicController.titleText
}
}
这将在数据控制器为 BasicCellDataController
时,设置一个 UITableViewCell
的 textLabel
属性的文本。请注意,如果我们有更多的数据控制器,这个函数需要区分它们。
现在我们有了数据控制器和准备操作的单元格,我们可以使用以下初始化方法之一创建一个 DataSourceController
的实例。
init(row: Any, delegate: DataSourceControllerDelegate?)
init(rows: [Any], delegate: DataSourceControllerDelegate?)
init(section: Section, delegate: DataSourceControllerDelegate?)
init(sections: [Section], delegate: DataSourceControllerDelegate?)
这些初始化器覆盖了你可能需要的所有可能情况。
- 用于列表的单个对象,没有头部或尾部。
- 要列表的对象包含在数组中,我们不需要头部或尾部。使用单个对象的数组与上一个情况等效。
- 我们需要一个单独的区段,具有头部、尾部或两者都有。我们也可以省略两者,从而回到上一个情况。
- 我们需要几个按给定数组顺序排列的区段,其中一些可以有一个或多个头部、尾部或两者都有。我们也可以省略所有的头部和尾部。
注意:代理是可选的,实际上可以省略。它也可以在创建后分配。我们将在后面描述它。
一旦我们有了实例 dataSourceControllerInstance
,我们需要注册我们的数据控制器以使用一个特定的数据模型,如下所示。
dataSourceControllerInstance.register(dataController: BasicCellDataController.self, for: String.self)
最后,只需要将 dataSource
属性分配给我们的 UITableView
或 UICollectionView
,如下所示。
myTableView.dataSource = dataSourceControllerInstance
...
myCollectionView.dataSource = dataSourceControllerInstance
下面是一个完整的简单例子
import DataSourceController
import UIKit
class TableViewController: UIViewController {
@IBOutlet private var tableView: UITableView!
private var rowEntries: [String] = ["First row", "Second row", "Third row", "Fourth row"]
private lazy var dataSourceController: DataSourceController = {
let controller = DataSourceController(sections: rowEntries)
controller.register(dataController: BasicCellDataController.self, for: String.self)
return controller
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = dataSourceController
tableView.reloadData()
}
}
其中 BasicCellDataController
上面已有描述。
以上就是所有!当屏幕加载时,DataSourceController
将自动渲染表格视图。
区段模型
在 DataSourceController
的实现中,Section
是正在使用的主体模型对象。即使是接受一个 Any
数组的初始化器,也创建了一个 Section
的实例。一个 Section
简单地说是一个以下类的实例。
class Section {
var rows: [Any]
var sectionData: SectionDataModelType?
init(sectionData: SectionDataModelType? = nil, rows: [Any])
}
注意,在初始化一个 Section
实例时,sectionData
参数是可选的,可以省略。
protocol SectionDataModelType {
var header: String? { get }
var footer: String? { get }
}
SectionDataModelType
是符合以下协议的任何对象
也就是说,带有特定区段头部和尾部的数据。
struct SectionDataModel: SectionDataModelType {
private(set) var header: String?
private(set) var footer: String?
init(header: String? = nil, footer: String? = nil)
}
对于大多数用例,框架提供的默认协议实现已经足够了
在 DataSourceController
的初始化方法中传入的 delegate
参数是符合以下协议的任何对象
protocol DataSourceControllerDelegate: AnyObject {
func backgroundMessageLabel(for view: UIView) -> UILabel?
func backgroundEmptyView(for view: UIView) -> UIView?
func dataSourceWasMutated(_: DataSourceController)
func dataSourceWasMutated(_: DataSourceController, section: Int)
func dataSourceWasMutated(_: DataSourceController, rows: [IndexPath])
}
所有函数都有一个默认的空实现,以使它们成为可选协议要求。
也就是说,代理主要通知由API(见下文API参考中详细信息)中可用的变异函数触发的DataSourceController
实例的变化。通常情况下,代理会是拥有UITableView
或UICollectionView
实例的对象,因为很可能显示列表的一部分需要重新加载。
此外,如果DataSourceController
实例的totalRowCount
属性为0(表示没有行),控制器将尝试显示由该函数提供的UIView
实例
func backgroundEmptyView(for view: UIView) -> UIView?
作为背景。请注意,此函数是可选的,因此可以省略。参数view
是具有DataSourceController
作为其dataSource
的UITableView
或UICollectionView
实例。
注意:从版本1.2开始,协议声明中已删除backgroundMessageLabel(for:) -> UILabel?
委托函数,无法使用。请使用backgroundEmptyView(for:) -> UIView?
函数。
UITableView头部和尾部
UITableView
实例中的节标题和尾部由框架使用符合协议的特定控制器自动管理
protocol SectionDataControllerType {
var headerTitle: String? { get }
var footerTitle: String? { get }
}
目前,框架确实使用了一个协议的内置实现
class SectionDataController: SectionDataControllerType {
private(set) var headerTitle: String?
private(set) var footerTitle: String?
init(header: String?, footer: String?)
convenience init(_ model: SectionDataModelType)
}
尽管SectionDataController
是一个公开类,因此可以在框架外进行子类化,但DataSourceController
的当前实现不允许使用任何其他类来显示UITableView
头部/尾部。目前正在开发一种提供此类程度定制的方法。
UICollectionView头部和尾部
UICollectionView
实例的头部和尾部在任何应用程序中都必须以不同的方式实现。因此,框架将UICollectionView
实例的头部和尾部视为常规单元格。因此,必须实现并注册一个符合SectionDataModelType
协议的模型对象的数据控制器。请注意,在这种情况下,可以使用自定义的节数据模型。
示例应用程序包含了一个UICollectionView
头部类的实现示例及其与DataSourceController
一起的使用方式。
API参考
完整的API参考可以在此处找到 API Reference。
示例应用
本存储库包含iOS示例应用。示例应用包含了UITableView
和UICollectionView
的实现示例。此外,为了进行比较,MenuViewController
类在其UITableView
内容显示上没有使用DataSourceController
。
贡献
此框架基于MVVMDataSource开发。与“父”框架的主要区别在于更简单的方法、移除MVVM设计模式(使其无架构)、CoreData部分(尽管可以使用它们)以及操作。这种简单方法旨在满足我们自己的项目的需求,并使其在MVVM设计模式之外使用,但我们一旦满足需求(添加适当的文档)就停止了。如果您有任何改进想法,我们将很高兴听到/阅读。只需打开一个新的问题来进一步讨论,或者打开一个带有您想法的pull request。
代码风格指南和格式化器
我们遵循Ray Wenderlich Swift Style Guide,除了空白区域部分:我们使用4个空格而不是2个进行缩进。
为了执行上述代码风格指南中的指南,我们使用SwiftFormat。一组规则已检查到本存储库中的文件.swiftformat
。在推送任何代码之前,请按照上述存储库中的如何安装说明安装SwiftFormat
,并在项目的根目录中执行以下命令
swiftformat . --config .swiftformat --swiftversion 5.0 --exclude Package.swift
这将重新格式化项目文件夹内的所有*.swift
文件,以遵循指南,但除外Package.swift
声明文件。
持续集成和部署
我们使用 Travis CI 作为我们的持续集成解决方案,在开放式拉取请求和合并到 master
分支时运行构建和测试。合并任何拉取请求到 master
分支前,测试必须通过。当推送新的标签时,Travis CI 还负责将库部署到 Cocoapods 的 Trunk 存储库。