SwiftyListKit
特性
- 用于创建简单和复杂列表视图的可声明方法
- 自定义重新加载/更新动画
- 带有模糊效果的内置加载视图:
ListLoader
- 集成了 O(N) 差异算法(由
DifferenceKit
框架提供) - 完全支持
UITableView
- 完全支持
UICollectionView
依赖
SwiftyListKit 依赖于 DifferenceKit。DifferenceKit 是一个针对 Swift 收集合成的快速灵活的 O(n) 差异算法框架。
要求
Swift 5.0, iOS >= 10.0
安装
CocoaPods
是Cocoa项目的依赖管理器。有关使用和安装说明,请访问他们的网站。要使用CocoaPods将SwiftyListKit集成到您的Xcode项目中,请在您的Podfile中添加以下条目
pod 'SwiftyListKit'
然后运行pod install
。
在您希望使用SwiftyListKit的任何文件中,别忘了使用import SwiftyListKit
导入框架。
手动安装
- 只需将
SwiftyListKit
文件夹放入您的项目中。 - 您现在可以使用
SwiftyListKit
了!
组件
SwiftyListKit条目由4个主要组件组成
- UI部分(
UITableViewCell
,UITableViewHeaderFooterView
,……) - 数据模型(
ListItemDataModel
) - 映射器(
function
) - 样式(
ListItemStyle
)
UI部分
为了使UITableViewCell
适应SwiftyListKit,您需要在您的cell中添加一个协议TableItem
(或CollectionItem
用于UICollectionViewCell
),例如
class OneTitleTableViewCell: UITableViewCell, TableItem {
}
您需要为UITableViewHeaderFooterView
和UICollectionReusableView
做同样的事情。
此外,如果您的列表项有任何委托方法,例如您有一个包含按钮的单元格,您需要将协议ListItemDelegatable
添加到您的列表项(单元格、页脚/页眉、可重用视图)中,并将协议Delegatable
添加到您的委托协议中。然后,在您的列表项类中,您需要按照以下方式实现来自ListItemDelegatable
协议的方法func set(delegate: Delegatable)
protocol ButtonTableViewCellDelegate: Delegatable {
func buttonTableViewCellDidPressButton(_ cell: ButtonTableViewCell)
}
class ButtonTableViewCell: UITableViewCell, TableItem, ListItemDelegatable {
weak var delegate: ButtonTableViewCellDelegate?
public func set(delegate: Delegatable) {
self.delegate = delegate as? ButtonTableViewCellDelegate
}
@IBAction func handleAction(_ sender: Any) {
delegate?.buttonTableViewCellDidPressButton(self)
}
}
数据模型
ListItemDataModel
是一个容器,包含了用于在列表中显示项所需的数据。ListItemDataModel
负责计算hashString
——您的列表项的唯一id
。hashString
会自动计算,但在某些情况下,您可能需要更改默认计算,因此您可以在数据模型中重写它。
数据模型的示例
struct TextDataModel: ListItemDataModel {
var tag: Any?
var text: String
init(tag: String, text: String) {
self.tag = tag
self.text = text
}
}
如果您想要区分不同的对象,请将协议变量tag
添加到您的数据模型中。例如,当您需要响应didSelectRow
动作时,可能会有所需要。
注意:ListItemDataModel
应只包含plain
数据,而不包含业务对象。
映射器
映射器——这只是将您的数据模型映射到您的UI项的函数。例如
func map(model: TextWithIconDataModel, cell: IconWithTitleTableViewCell) {
cell.iconImageView.setImage(fromUrl: model.iconUrl)
cell.titleLabel.text = model.text
}
如何实现map
函数完全取决于您,这里提供了一个示例来存储它们
protocol Mapper {
associatedtype TableItem: ListItem
associatedtype Data: ListItemDataModel
static func map(data: Data, cell: TableItem)
}
struct TitleCellMapper: Mapper {
static func map(data: TextDataModel, cell: OneTitleTableViewCell) {
cell.titleLabel.text = data.text
}
}
样式
ListItemStyle
是一个结构体,有助于为列表项应用样式。而不是配置,例如单元格中标签的颜色,您创建一个样式并将其应用于列表项。
示例
extension ListItemStyle where T: OneTitleTableViewCell {
static var `default`: ListItemStyle<T> {
return ListItemStyle<T> {
$0.titleLabel.textColor = .black
$0.titleLabel.font = .boldSystemFont(ofSize: 12.0)
}
}
static var error: ListItemStyle<T> {
return ListItemStyle<T> {
$0.titleLabel.textColor = .red
$0.titleLabel.font = .boldSystemFont(ofSize: 16.0)
}
}
}
列表项视图模型
我们已经讨论了组成列表项的4个组件,现在是时候将它们放在一起了
let textDataModel = TextDataModel(title: "some text here")
let viewModel = TableItemViewModel(data: textDataModel,
map: TitleCellMapper.map,
style: .error)
此视图模型表示带有红色颜色文本的"some text here"
的OneTitleTableViewCell
,并在UITableView
中。如果想要颜色不同或背景颜色不同,只需更改样式。或者,如果您想更改标签中的文本,只需更改数据模型,或者如果您想更改UI(单元格)— 那就只需更改映射器。所有组件都易于更改。
用法说明
让我们构建一个带有表格视图和若干单元格的 ViewController。我们可以通过继承 BaseAnimatedTableViewController
实现这一点,或者我们可以直接将 AnimatedTableListProtocol
协议添加到我们的 ViewController 中
注意:如果您使用了协议,那么必须在 viewDidLoad
中调用 setup(withTableStyle: .plain)
方法。
示例
class ControllerWithProtocolOnly: UIViewController, AnimatedTableListProtocol {
var tableView: UITableView!
var dataSource: TableViewDataSourceAnimated<TableListSection>!
var syncDelegate: SyncDelegate<TableListSection>!
override func viewDidLoad() {
super.viewDidLoad()
setup(withTableStyle: .plain)
reloadViewModels()
}
private func reloadViewModels() {
let sections = getSections()
self.update(with: sections, updateAnimation: .default)
}
// MARK: - Generate random rows/headers
private func getSections() -> [TableListSection] {
var rowViewModels: [TableItemViewModel] = []
let nameOfProductData = TextDataModel(text: "The Best Product")
let nameOfProductViewModel = TableItemViewModel(data: nameOfProductData, map: TitleCellMapper.map, style: .custom(.title))
let descriptionData = TextDataModel(text: "some description here")
let descriptionViewModel = TableItemViewModel(data: textData, map: TitleCellMapper.map, style: .custom(.description))
let productInfoSection = TableListSection(rows: [nameOfProductViewModel, descriptionData]])
let productIconDataModel = IconDataModel(iconUrl: "some url here")
let productViewModel = TableItemViewModel(data: productIconDataModel, map: IconCellMapper.map, style: .custom(.circle))
let productImageSection = TableListSection(rows: [productViewModel])
return [productInfoSection, productImageSection]
}
}
本例展示了如何实现一个 ViewController,其中包含具有 2 个部分的 TableView,其中第一个部分包含 2 个具有相同 UI 和 Map 函数但没有相同样式和数据的单元格,第二个部分包含 1 个单元格。
注意:您不需要注册单元格/标题/页脚/可重用视图,因为如果您使用 AnimatedTableListProtocol
,则 tableView
包含 TableViewRegistrator
,它会自动注册所有列表元素。您只需确保列表项的 reuseIdentifier 与列表项名称匹配(仅适用于 UITableViewCell
和 UICollectionViewCell
,对于标题/页脚/可重用视图则无所谓)。
同步委托
如果您需要使用 TableView 委托方法,则可以使用 SyncDelegate 来实现。SyncDelegate 是 AnimatedTableListProtocol
的一部分(相应地也是 BaseAnimatedTableViewController
的一部分)。
在 SyncDelegate 中的委托方法是基于闭包的。
示例
class SyncDelegateExampleViewController: UIViewController, AnimatedTableListProtocol {
var tableView: UITableView!
var dataSource: TableViewDataSourceAnimated<TableListSection>!
var syncDelegate: SyncDelegate<TableListSection>!
override func viewDidLoad() {
super.viewDidLoad()
setup(withTableStyle: .plain)
syncDelegate.didSelectRow = { [weak self] _, _, model in
guard let tag = model?.data.tag as? String {
return
}
self?.showAlert(with: tag)
}
syncDelegate.onEditActions = { [weak self] _, _, _ in
guard let self = self else { return nil }
return [.init(style: .destructive, title: "Delete", handler: self.handleItemDelete)]
}
}
}
此外,SyncDelegate 中还包含所有 ScrollView 委托方法(例如 onScrollViewDidScroll、onScrollViewWillBeginDragging、onScrollViewDidZoom 等)。
致谢
SwiftyListKit 由 Alexander Shoshiashvili 和 Dmitrii Grebenshchikov 编写。
该项目使用了 DifferenceKit。
许可协议
SwiftyListKit 在 MIT 许可协议下发布。有关详细信息,请参阅 LICENSE。