#apps
May 24, 2017

SHMTableView vs. UITableViews

Ondřej Macoszek
Ondřej Macoszek
iOSswiftopensource
SHMTableView vs. UITableViews

In the previous article we introduced the SHMTableView library. What we’d now like to do is make a practical comparison between two different approaches to implementing tables with multiple cell types.

The new design of the Showmax app detail screen was aimed to ease browsing through multiple seasons and episodes. We want to eliminate unnecessary steps, so that the user can commence watching as quickly as possible.
An important part of redesign was making episodes as a list. Tapping on an episode would show the new video preview below. For our example, we’ll be making a super simple list of episodes with video previews:

Post image

Preparing common stuff

For both cases, the first step is to create the following common objects. You can explore them more
closely in this example code on GitHub.

  • view models
    undefinedundefined
  • view cells conforming to the SHMTableRowProtocol
    undefinedundefinedundefinedundefined

Implementing the screen

UITableView

class ComparisonUITableViewController: UIViewController
{
    @IBOutlet open weak var tableView: UITableView!

    var items: [Any] = []

    override func viewDidLoad()
    {
        items = [
            EpisodeCellViewModel(number: 7, title: "The One With The Race Car Bed"),
            EpisodeCellViewModel(number: 8, title: "The One With The Giant Poking Device"),
            EpisodeCellViewModel(number: 9, title: "The One With The Football"),
            VideoCellViewModel(title: "Video preview for 'The One With The Football'"),
            EpisodeCellViewModel(number: 10, title: "The One Where Rachel Quits"),
            EpisodeCellViewModel(number: 11, title: "The One Where Chandler Can't Remember Which Sister"),
        ]

        tableView.delegate = self
        tableView.dataSource = self
        tableView.estimatedRowHeight = 44

        tableView?.register(UINib(nibName: "EpisodeTableViewCell", bundle: nil), forCellReuseIdentifier: "EpisodeTableViewCell")
        tableView?.register(UINib(nibName: "VideoTableViewCell", bundle: nil), forCellReuseIdentifier: "VideoTableViewCell")
    }
}

extension ComparisonUITableViewController: UITableViewDataSource
{
    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return items.count
    }

    public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        guard indexPath.row < items.count else
        {
            fatalError("Requesting cell on indexPath \(indexPath) out of bounds.")
        }

        let item = items[indexPath.row]

        if  let episode = item as? EpisodeCellViewModel,
            let cell = tableView.dequeueReusableCell(withIdentifier: "EpisodeTableViewCell", for: indexPath) as? EpisodeTableViewCell
        {
            cell.configure(episode)
            return cell

        } else if   let video = item as? VideoCellViewModel,
                    let cell = tableView.dequeueReusableCell(withIdentifier: "VideoTableViewCell", for: indexPath) as? VideoTableViewCell
        {
            cell.configure(video)
            return cell
        }

        fatalError("Cannot create cell for requested indexPath \(indexPath).")
    }
}

extension ComparisonUITableViewController: UITableViewDelegate
{
    public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
    {
        guard indexPath.row < items.count else
        {
            return
        }

        let item = items[indexPath.row]

        if  let episode = item as? EpisodeCellViewModel,
            let cell = tableView.dequeueReusableCell(withIdentifier: "EpisodeTableViewCell", for: indexPath) as? EpisodeTableViewCell
        {
            cell.configureAtWillDisplay(episode)

        } else if   let video = item as? VideoCellViewModel,
                    let cell = tableView.dequeueReusableCell(withIdentifier: "VideoTableViewCell", for: indexPath) as? VideoTableViewCell
        {
            cell.configureAtWillDisplay(video)
        }
    }

    public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    {
        guard indexPath.row < items.count else
        {
            return
        }

        let item = items[indexPath.row]

        if let episode = item as? EpisodeCellViewModel
        {
            episode.openDetail()

        } else if let video = item as? VideoCellViewModel
        {
            video.play()
        }
    }
}

SHMTableView

class ComparisonSHMTableViewController: UIViewController
{
    public var shmTable: SHMTableView!
    @IBOutlet open weak var tableView: UITableView!

    var items: [Any] = []

    override func viewDidLoad()
    {
        items = [
            EpisodeCellViewModel(number: 7, title: "The One With The Race Car Bed"),
            EpisodeCellViewModel(number: 8, title: "The One With The Giant Poking Device"),
            EpisodeCellViewModel(number: 9, title: "The One With The Football"),
            VideoCellViewModel(title: "Video preview for 'The One With The Football'"),
            EpisodeCellViewModel(number: 10, title: "The One Where Rachel Quits"),
            EpisodeCellViewModel(number: 11, title: "The One Where Chandler Can't Remember Which Sister"),
        ]

        let rows = items.flatMap({ item -> SHMTableRowProtocol? in

            if let episode = item as? EpisodeCellViewModel
            {
                return SHMTableRow<EpisodeTableViewCell>(model: episode, action: { _ in episode.openDetail() })

            } else if let video = item as? VideoCellViewModel
            {
                return SHMTableRow<VideoTableViewCell>(model: video, action: { _ in video.play() })

            } else
            {
                return nil
            }
        })

        shmTable = SHMTableView(tableView: tableView)
        shmTable += SHMTableSection(rows: rows)
    }
}

Using plain UITableViewDataSource and UITableViewDelegate

The biggest challenge here is that in each delegate or datasource method you often need to check
the type of displayed cell in order to perform some specific logic with it. When there are many cell types
this means a lot of type checking. In some instances, the code might multiply with each additional delegate/datasource method.
And things can get even more complex when you need perform batch table updates (add/remove/reload).

Using SHMTableView

Basically, the process is to map your existing cell models to cell view types and pass them to the SHMTableView.
This will then take care of cell registration, handle table data sources to produce the correct cell view
and fill this with the corresponding model. In this manner, SHMTableView can be used to avoid repetitive busywork.

Comparison

UITableViewSHMTableViewProsReadable for 1-2 cell typesDirect access to delegate and data sourcesAll implementations in one placeReadable for 2 or more cell typesController is uncluttered with common and repetitive UITableView routinesFocuses on the model structure to be displayed in the tableMore flexibility in adding and removing cellsConsLeads to massive view controllers
for 3 or more cell typesCode duplication within view controllerchecking types of cells, sections, section headers, section footers and modelsCode duplication within other similar table-centric screens, especially:animating changed rowssetting up self-sizingRequires mapping of cell model to cell viewNon-direct access to implemented UITableView protocols (but we plan to provide a way to extend our implementations with specific code, so your code gets called)ResultsWhile it’s possible to develop the same screen with UITableView, when compared to SHMTableView it takes longer and requires more steps. There’s also a risk of forgetting some routine steps. Code duplication might result in the need to make changes in multiples places when refactoring.Using SHMTableView we can implement the screen faster and with considerably less code repetition. This also allows us to create a separate model and view for each cell type that could be reused on other screens as well.

Post image

Get involved

You can play with the code yourself. The code used in this article is available at GitHub. You can also download SHMTableView from our official repository.

Come and help us improve the SHMTableView library, contact us at geeks@showmax.com.

Share article via: