CellViewModel
使用 CellViewModel 配置您 的 UITableViewCell 或 UICollectionViewCell 是与 UIKit 集合一起工作的一种可能的工作方式。
要求
- iOS 9.0+
- Xcode 10.0+
- Swift 4.2+
安装
CocoaPods
target 'MyApp' do
pod 'CellViewModel', '~> 1.7.9'
end
Carthage
github "AntonPoltoratskyi/CellViewModel" "master"
用法
支持UITableView & UICollectionView - 一种可能的方法,受CocoaHeads启发
您可以将UITableViewCell
或UICollectionViewCell
的配置逻辑从-cellForRowAtIndexPath:
移到单独的类型。
原生设置
- 创建符合
CellViewModel
类型的单元类和适当的类型
public typealias AnyViewCell = UIView
public protocol CellViewModel: AnyCellViewModel {
associatedtype Cell: AnyViewCell
func setup(cell: Cell)
}
UserTableViewCell.swift
import CellViewModel
// MARK: - View Model
struct UserCellModel: CellViewModel {
var user: User
func setup(cell: UserTableViewCell) {
cell.nameLabel.text = user.name
}
}
// MARK: - Cell
final class UserTableViewCell: UITableViewCell, XibInitializable {
@IBOutlet weak var nameLabel: UILabel!
}
- 注册创建的模型类型
tableView.register(UserCellModel.self)
通过注册模型类型,会检查单元类是否符合XibInitializable,以便注册UINib
或只是单元类的类型。
- 然后将您的模型存储在数组(或您自定义的数据源类型)中
private var users: [AnyCellViewModel] = []
AnyCellViewModel是CellViewModel
的基协议。它只需要为了修复编译器的限制,因为您只能将具有关联类型的协议用作泛型约束,并且不能编写如下内容
private var users: [CellViewModel] = [] // won't compile
UITableViewDataSource
的实现非常简单,即使你有多种单元类型,因为所有'cellForRow'逻辑都包含在我们的视图模型中
import CellViewModel
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
private var users: [AnyCellViewModel] = []
override func viewDidLoad() {
super.viewDidLoad()
users = User.testDataSource.map { UserCellModel(user: $0) }
tableView.register(nibModel: UserCellModel.self)
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(with: tableModel(at: indexPath), for: indexPath)
}
private func tableModel(at indexPath: IndexPath) -> AnyCellViewModel {
return users[indexPath.row]
}
}
快速设置
使用现有的适配器以执行快速设置。
- 对于
UITableView
- TableViewDataAdapter
private lazy var adapter = TableViewDataAdapter(tableView: self.tableView)
// ...
func setup(users: [AnyCellViewModel]) {
adapter.data = users
}
更新data
属性将调用reloadData()
。
- 对于
UICollectionView
- CollectionViewDataAdapter
private lazy var adapter = CollectionViewDataAdapter(tableView: self.collectionView)
// ...
func setup(users: [AnyCellViewModel]) {
adapter.data = users
}
这两个适配器已按照相应的数据源协议进行适配:UICollectionViewDataSource
和UITableViewDataSource
。
基础视图控制器
最简单的方法是继承自BaseCollectionViewController
。
有时您需要表格UI,但有一些独特的节内边距或项间间距。为此,BaseCollectionViewController
提供了对UICollectionViewDelegateFlowLayout
协议的原生实现,以满足您的表格UI。
用法
final class UsersViewController: BaseCollectionViewController {
@IBOutlet weak var collectionView: UICollectionView {
didSet {
// initialize reference in base class
_collectionView = collectionView
}
}
override var viewModels: [AnyCellViewModel.Type] {
return [
UserCellModel.self,
// ... add more
]
}
override var supplementaryModels: [AnySupplementaryViewModel.Type] {
return [
UserHeaderModel.self,
/// ... add more
]
}
// ... your domain code
BaseCollectionViewController
是CollectionViewDataAdapter
的包装器,因此它已经设置了方法
open func setup(_ sections: [Section]) {
adapter.data = sections
}
Section
类型是容器,用于存储头、尾和项目模型的布局信息,如间距等。
public final class Section {
public var insets: UIEdgeInsets?
public var lineSpacing: CGFloat?
public var header: AnySupplementaryViewModel?
public var footer: AnySupplementaryViewModel?
public var items: [AnyCellViewModel]
/// ...
}
为了允许自动推断使用的视图模型类型而不是显式地在 viewModels
和 supplementaryModels
属性中声明它们,请覆盖 automaticallyInferCellViewModelTypes
。
override var automaticallyInferCellViewModelTypes: Bool {
return true
}
无障碍访问
有时需要为 UI 测试定义 accessibilityIdentifier
。
存在一个符合 CellViewModel 协议的 Accessible 协议。
public protocol Accessible {
var accessibilityIdentifier: String? { get }
var accessibilityOptions: AccessibilityDisplayOptions { get }
}
因此,您需要在模型类型实现中定义 accessibilityIdentifier
属性。
struct UserCellModel: CellViewModel {
var accessibilityIdentifier: String? {
return "user_cell"
}
// ...
}
如果需要,可以定义 accessibilityOptions
以在 accessibilityIdentifier
的末尾添加索引路径作为后缀。
struct UserCellModel: CellViewModel {
var accessibilityIdentifier: String? {
return "user_cell"
}
var accessibilityOptions: AccessibilityDisplayOptions {
return [.row, .section]
}
// ...
}
许可协议
CellViewModel 在 MIT 许可协议下可用。有关更多信息,请参阅 LICENSE 文件。