SwiftyListKit 1.0.24

SwiftyListKit 1.0.24

Alexander Shoshiashvili 维护。



  • 作者
  • Alexander Shoshiashvili

Platform: iOS 10+ Language: Swift 5 Cocoapods compatible License: MIT

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个主要组件组成

  1. UI部分(UITableViewCellUITableViewHeaderFooterView,……)
  2. 数据模型(ListItemDataModel
  3. 映射器(function
  4. 样式(ListItemStyle

UI部分

为了使UITableViewCell适应SwiftyListKit,您需要在您的cell中添加一个协议TableItem(或CollectionItem用于UICollectionViewCell),例如

class OneTitleTableViewCell: UITableViewCell, TableItem {
}

您需要为UITableViewHeaderFooterViewUICollectionReusableView做同样的事情。

此外,如果您的列表项有任何委托方法,例如您有一个包含按钮的单元格,您需要将协议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——您的列表项的唯一idhashString会自动计算,但在某些情况下,您可能需要更改默认计算,因此您可以在数据模型中重写它。

数据模型的示例

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 与列表项名称匹配(仅适用于 UITableViewCellUICollectionViewCell,对于标题/页脚/可重用视图则无所谓)。

同步委托

如果您需要使用 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 ShoshiashviliDmitrii Grebenshchikov 编写。

该项目使用了 DifferenceKit

许可协议

SwiftyListKit 在 MIT 许可协议下发布。有关详细信息,请参阅 LICENSE