Swift 5.1 Platforms iOS CocoaPods Carthage SPM License MIT

AEViewModel

用于便捷创建表格视图和集合视图的 Swift 小助手

我为个人使用制作了这个库,但也欢迎随意使用或贡献。更多示例请查看 SourcesTests

AEViewModel

索引

简介

几乎所有应用程序的界面都经常在表格视图或集合视图中进行。这是我为了让自己更轻松地完成这项任务而做的尝试,希望对其他人也有帮助。

这个解决方案背后的想法非常抽象,意味着它可以应用于许多不同的方式(或风格),这些方式最适合你的情况或喜好。

通过正确使用这个框架,它将迫使你通过利用 MVVM 模式的概念来编写更清晰和可维护的代码。为了更好地理解它,你应该首先熟悉 “手动”方法

第一眼看上去可能对(所有人来说)不是快速和容易掌握的,但是如果你给它一个适当的机会,你可能再也不想在不使用它的情况下创建另一个表格视图或集合视图,我只是说说而已...

特性

用法

DataSource

我建议从熟悉 DataSource.swift 开始,因为你基本上所有事情都会用到它。

这些只是非常简单的协议,从 DataSource 开始,它必须有 sections(区),然后 Section 必须有 items(项),其中每个 Item 包含 identifier: StringviewModel: ViewModelchild: DataSource?

/// ViewModel is whatever you choose it to be, easy like this:
struct MyCustomWhatever: ViewModel {}

BasicDataSource

BasicDataSource.swift 中有一些符合所有这些协议的简单结构体,并且大多数时候应该可以直接使用这些结构体。由于这些结构体也符合 Codable,因此也可以例如从 JSON 数据创建 BasicDataSource

如果需要更具体的东西,请创建符合这些协议的自定义类型并使用它们。

Cell

Cell.swift 中有一个简单的协议,它同时用于表格视图和集合视图单元格。请注意 TableCellCollectionCell 只是简单的类型别名

public typealias TableCell = UITableViewCell & Cell
public typealias CollectionCell = UICollectionViewCell & Cell

在创建自定义单元格时,最简单的方法是从 TableCellBasicCollectionCellBasic 继承子类,并重写此协议中的方法

/// Called in `init` and `awakeFromNib`, configure outlets and layout here.
func configure()

/// Called in `configure` and `prepareForReuse`, reset interface here.
func reset()

/// Called in `tableView(_:cellForRowAt:)`, update interface with view model here.
func update(with item: Item)

/// Called in `tableView(_:didSelectRowAt:)` and whenever specific cell calls it (ie. toggle switch).
/// By default this call will be forwarded to `delegate` (after setting some `userInfo` optionally).
/// If needed, call this where it makes sense for your cell, or override and call `super` at some moment.
func callback(_ sender: Any)

TableCell

开箱即用地提供了一些常用的表格视图单元格

public enum TableCellType {
    case basic
    case subtitle
    case leftDetail
    case rightDetail
    case toggle
    case toggleWithSubtitle
    case slider
    case sliderWithLabels
    case textField
    case textView
    case button
    case spinner
    case customClass(TableCell.Type)
    case customNib(TableCell.Type)
}

CollectionCell

虽然对于集合视图单元格,你可能想要创建更自定义的东西... :)

public enum CollectionCellType {
    case basic
    case button
    case spinner
    case customClass(CollectionCell.Type)
    case customNib(CollectionCell.Type)
}

TableViewController

这个故事的最后一部分是 TableViewController,你猜对了,它继承自 UITableViewController

只有这个足够好,只需配置其 dataSource 属性并重写这些方法,就可以注册、出列和更新你将需要的所有单元格

/// - Note: Return proper cell type for the given item identifier.
/// Based on this it knows which cells to register for which identifier.
open func cellType(forIdentifier identifier: String) -> TableCellType {
    return .basic
}

/// - Note: Update cell at the given index path. 
/// `TableViewController` does this by default, so if that's enough for your case just skip this,
/// otherwise call `super.update(cell, at: indexPath)` and add custom logic after that.
open func update(_ cell: TableCell, at indexPath: IndexPath) {
    let item = viewModel.item(at: indexPath)
    cell.update(with: item)
    cell.delegate = self
}

/// - Note: Handle action from cell for the given index path.
/// This will be called in `tableView(_:didSelectRowAt:)` or when `callback(_:)` is called
open func action(for cell: TableCell, at indexPath: IndexPath, sender: Any) {}

CollectionViewController

这几乎是 TableViewController 的副本,但它使用的是 CollectionCell,就是这样。

示例

你应该看看 示例项目,但这里有一个快速预览

import AEViewModel

struct ExampleDataSource: DataSource {
    struct Id {
        static let cells = "cells"
        static let form = "form"
        static let settings = "settings"
        static let github = "github"
    }

    var title: String? = "Example"
    var sections: [Section] = [
        BasicSection(footer: "Default cells which are provided out of the box.", items: [
            BasicItem(identifier: Id.cells, title: "Cells")
        ]),
        BasicSection(header: "Demo", items: [
            BasicItem(identifier: Id.form, title: "Form", detail: "Static Data Source"),
            BasicItem(identifier: Id.settings, title: "Settings", detail: "JSON Data Source"),
            BasicItem(identifier: Id.github, title: "Github", detail: "Remote Data Source")
        ])
    ]
}

final class ExampleTVC: TableViewController {
    
    typealias Id = ExampleDataSource.Id
    
    // MARK: Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        dataSource = ExampleDataSource()
    }
    
    // MARK: Override
    
    override func cellType(forIdentifier identifier: String) -> TableCellType {
        return .subtitle
    }
    
    override func update(_ cell: TableCell, at indexPath: IndexPath) {
        super.update(cell, at: indexPath)
        cell.accessoryType = .disclosureIndicator
    }

    override func action(for cell: TableCell, at indexPath: IndexPath, sender: Any) {
        switch dataSource.identifier(at: indexPath) {
        case Id.cells:
            show(CellsTVC(), sender: self)
        case Id.form:
            show(FormTVC(), sender: self)
        case Id.settings:
            show(MainSettingsTVC(), sender: self)
        case Id.github:
            show(GithubTVC(), sender: self)
        default:
            break
        }
    }
    
}

安装

许可证

此代码在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE