CellViewModel 1.8.0

CellViewModel 1.8.0

Anton Poltoratskyi维护。



  • 作者
  • Anton Poltoratskyi

Swift Xcode MIT CocoaPods Compatible

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启发

您可以将UITableViewCellUICollectionViewCell的配置逻辑从-cellForRowAtIndexPath:移到单独的类型。

原生设置

  1. 创建符合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!
}
  1. 注册创建的模型类型
tableView.register(UserCellModel.self)

通过注册模型类型,会检查单元类是否符合XibInitializable,以便注册UINib或只是单元类的类型。

  1. 然后将您的模型存储在数组(或您自定义的数据源类型)中
private var users: [AnyCellViewModel] = []

AnyCellViewModelCellViewModel的基协议。它只需要为了修复编译器的限制,因为您只能将具有关联类型的协议用作泛型约束,并且不能编写如下内容

private var users: [CellViewModel] = [] // won't compile
  1. 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]
    }
}

快速设置

使用现有的适配器以执行快速设置。

  1. 对于UITableView - TableViewDataAdapter
private lazy var adapter = TableViewDataAdapter(tableView: self.tableView)

// ...

func setup(users: [AnyCellViewModel]) {
    adapter.data = users
}

更新data属性将调用reloadData()

  1. 对于UICollectionView - CollectionViewDataAdapter
private lazy var adapter = CollectionViewDataAdapter(tableView: self.collectionView)

// ...

func setup(users: [AnyCellViewModel]) {
    adapter.data = users
}

这两个适配器已按照相应的数据源协议进行适配:UICollectionViewDataSourceUITableViewDataSource

基础视图控制器

最简单的方法是继承自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

BaseCollectionViewControllerCollectionViewDataAdapter的包装器,因此它已经设置了方法

    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]
    
    /// ...
}

为了允许自动推断使用的视图模型类型而不是显式地在 viewModelssupplementaryModels 属性中声明它们,请覆盖 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 文件。