Latest 1.1.0
Homepage https://github.com/AntonPoltoratskyi/CellViewModel
License MIT
Platforms ios 10.0
Frameworks UIKit

Swift
Xcode
MIT
CocoaPods Compatible

Using CellViewModel to configure you UITableViewCell or UICollectionViewCell is just a one possible approach of work with UIKit’s collections.

Requirements:

  • iOS 10.0+
  • Xcode 10.0+
  • Swift 4.2+

Installation

CocoaPods

target 'MyApp' do
  pod 'CellViewModel', '~> 1.1'
end

Carthage

github "AntonPoltoratskyi/CellViewModel" "master"

Usage

Works with UITableView & UICollectionView – one possible approach, inspired by CocoaHeads:

You can move configuration logic for UITableViewCell or UICollectionViewCell from -cellForRowAtIndexPath: to separate types.

1) You need to create cell class and appropriate type that conforms to CellViewModel type:

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!
}

2) After that you need to register created model type:

There are 2 options:

  • use register(nibModel:) if appropriate CellViewModel‘s Cell conforms to XibInitializable:

    tableView.register(nibModel: UserCellModel.self)
  • otherwise use register(viewModel:):
    tableView.register(viewModel: UserCellModel.self)

3) Then store your models in array (or your custom datasource type):

private var users: [AnyCellViewModel] = []

AnyCellViewModel is a base protocol of CellViewModel.
It’s needed only in order to fix compiler limitation as you can use protocols with associatedtype only as generic constraints and can’t write something like this:

private var users: [CellViewModel] = [] // won't compile

4) UITableViewDataSource implementation is very easy, even if you have multiple cell types, because all logic are contained in our view models:

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(withModel: tableModel(at: indexPath), for: indexPath)
    }

    private func tableModel(at indexPath: IndexPath) -> AnyCellViewModel {
        return users[indexPath.row]
    }
}

5) Or use TableViewDataAdapter:

private lazy var adapter = TableViewDataAdapter(tableView: self.tableView)

assign it as UITableView’s dataSource:

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.dataSource = adapter
}

updating data property will cause reloadData()

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

Accessibility

Sometimes there is a need to define accessibilityIdentifier for UI testing purposes.

There is Accessible protocol that is conformed by CellViewModel protocol.

public protocol Accessible {
    var accessibilityIdentifier: String? { get }
    var accessibilityOptions: AccessibilityDisplayOptions { get }
}

So you need to define accessibilityIdentifier property in your model type implementation:

struct UserCellModel: CellViewModel {

    var accessibilityIdentifier: String? {
        return "user_cell"
    }

    // ...
}

And define accessibilityOptions if needed to add index path as suffix in the end of accessibilityIdentifier:

struct UserCellModel: CellViewModel {

    var accessibilityIdentifier: String? {
        return "user_cell"
    }

    var accessibilityOptions: AccessibilityDisplayOptions {
        return [.row, .section]
    }

    // ...
}

License

CellViewModel is available under the MIT license. See the LICENSE file for more info.

Latest podspec

{
    "name": "CellViewModel",
    "version": "1.1.0",
    "summary": "CellViewModel is a protocol that includes logic for reuse, accessibility and dequeue",
    "description": "Using CellViewModel to configure you UITableViewCell or UICollectionViewCell is just a one possible approach of work with UIKit's collections.",
    "homepage": "https://github.com/AntonPoltoratskyi/CellViewModel",
    "license": {
        "type": "MIT",
        "file": "LICENSE"
    },
    "authors": "Anton Poltoratskyi",
    "platforms": {
        "ios": "10.0"
    },
    "source": {
        "git": "https://github.com/AntonPoltoratskyi/CellViewModel.git",
        "tag": "1.1.0"
    },
    "source_files": [
        "CellViewModel/Sources",
        "CellViewModel/Sources/**/*.{swift}"
    ],
    "frameworks": "UIKit",
    "swift_version": "4.2"
}

Pin It on Pinterest

Share This