用于便捷创建表格视图和集合视图的 Swift 小助手
几乎所有应用程序的界面都经常在表格视图或集合视图中进行。这是我为了让自己更轻松地完成这项任务而做的尝试,希望对其他人也有帮助。
这个解决方案背后的想法非常抽象,意味着它可以应用于许多不同的方式(或风格),这些方式最适合你的情况或喜好。
通过正确使用这个框架,它将迫使你通过利用 MVVM 模式的概念来编写更清晰和可维护的代码。为了更好地理解它,你应该首先熟悉 “手动”方法。
第一眼看上去可能对(所有人来说)不是快速和容易掌握的,但是如果你给它一个适当的机会,你可能再也不想在不使用它的情况下创建另一个表格视图或集合视图,我只是说说而已...
我建议从熟悉 DataSource.swift 开始,因为你基本上所有事情都会用到它。
这些只是非常简单的协议,从 DataSource
开始,它必须有 sections(区),然后 Section
必须有 items(项),其中每个 Item
包含 identifier: String
,viewModel: ViewModel
和 child: DataSource?
。
/// ViewModel is whatever you choose it to be, easy like this:
struct MyCustomWhatever: ViewModel {}
在 BasicDataSource.swift 中有一些符合所有这些协议的简单结构体,并且大多数时候应该可以直接使用这些结构体。由于这些结构体也符合 Codable
,因此也可以例如从 JSON 数据创建 BasicDataSource
。
如果需要更具体的东西,请创建符合这些协议的自定义类型并使用它们。
在 Cell.swift 中有一个简单的协议,它同时用于表格视图和集合视图单元格。请注意 TableCell
和 CollectionCell
只是简单的类型别名
public typealias TableCell = UITableViewCell & Cell
public typealias CollectionCell = UICollectionViewCell & Cell
在创建自定义单元格时,最简单的方法是从 TableCellBasic
或 CollectionCellBasic
继承子类,并重写此协议中的方法
/// 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)
开箱即用地提供了一些常用的表格视图单元格
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)
}
虽然对于集合视图单元格,你可能想要创建更自定义的东西... :)
public enum CollectionCellType {
case basic
case button
case spinner
case customClass(CollectionCell.Type)
case customNib(CollectionCell.Type)
}
这个故事的最后一部分是 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) {}
这几乎是 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
}
}
}
.package(url: "https://github.com/tadija/AEViewModel.git", from: "0.9.2")
github "tadija/AEViewModel"
pod 'AEViewModel'
此代码在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE。