Reusable view for both UITableView, UICollection or another UIView

Cover Image for Reusable view for both UITableView, UICollection or another UIView

Yes, it's now 2022 and the latest ios version (at this time) is 16. But there are still projects that are still supporting iOS 12 and below. And there are still devs that are still composing cells on top UITableViewCell, in which on iOS 13, it can be just a collectionView and what if they have the same layout but in grid(as a collectionview cell) or as a subview of another view? then are we going to create a different view for that?

I'm going to show how solve this (not really a problem) repeatition and preparation of using UICollectionview as a list in the future (if incase the project stuck of using UIKit).


First, let's create a protocol named ConfigurableView. This protocol is simple and yet powerful enough to re-use your views and with this, this method can now be a standard convention for configuring views on your project.

protocol ConfigurableView: UIView {
  associatedtype ViewModel

  func configure(with viewModel: ViewModel)
}

Then create a UITableViewCell/UICollectionViewCell to wrap the configurable view.

final class UIViewTableWrapperCell<View: ConfigurableView>: UITableViewCell {

  private let view: View

  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    view = View()
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setupUI()
  }

  func configure(with viewModel: View.ViewModel) {
    view.configure(with: viewModel)
  }

  private func setupUI() {
    contentView.addSubview(view)
    view.translatesAutoresizingMaskIntoConstraints = false

    // Setup layout constraints
    NSLayoutConstraint.activate([
      view.topAnchor.constraint(equalTo: contentView.topAnchor),
      view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
      contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
      contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
    ])
  }
}

// ... Same pattern for UICollectionView Cell wrapper

Then, create a UITableView/UICollectionView extension for cell registration and deqeueing for simplicty.

// Provide a default **reuseIdentifier** equal to the class name.
private extension UITableViewCell {
  static var reuseIdentifier: String {
    String(describing: self)
  }
}

extension UITableView {

  func registerCell<Cell: UITableViewCell>(_ type: Cell.Type) {
    register(type, forCellReuseIdentifier: type.reuseIdentifier)
  }

  // MARK: Dequeue Table View Cell
  func dequeueCell<Cell: UITableViewCell>(_ type: Cell.Type, for indexPath: IndexPath) -> Cell {
    guard let cell = dequeueReusableCell(withIdentifier: type.reuseIdentifier) as? Cell else {
      fatalError("Unregistered cell: \(type.reuseIdentifier)")
    }
    return cell
  }

  // ... Similar pattern with Header/Footer view


Then let's create a sample view that is a subclass of UIView, so we can reuse this view anywhere.

class CustomView: UIView, ConfigurableView {
  typealias ViewModel = String

  private let namelabel = UILabel()

  // ... Setup your view

  func configure(with viewModel: ViewModel) {
    namelabel.text = viewModel
  }
}

and then to use this.

// To register
tableView.register(UIViewTableWrapperCell<CustomView>.self)

// To dequeue
let cell = tableView.dequeue(UIViewTableWrapperCell<CustomView>.self)
cell.configure(with: "Hello world")

That's it. :)