Table Views

How to properly create complex tableviews

Why this is important

Tableviews are at the heart of iOS and we see them everywhere. Sometimes it is hard to create a tableview with multiple sections and different cells within each section. For example, your multi-section code might look something like this:

    // MARK: - TABLE VIEW
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        switch indexPath.section {
        case 0:
            let cell = tableView.dequeueReusableCell...
            return cell
        case 1:
            let cell = tableView.dequeueReusableCell...
            return cell
        case 2:
            let cell = tableView.dequeueReusableCell...
            return cell
        default:
            return UITableViewCell()
        }
    }

What is wrong with this? We are switching on Int, which has a lot more than 3 cases. To combat this, we use the default case to silence the error.

If we want to add a new section, or move around existing sections, we have go in and rearrange/add new cases to each function where we switch on the indexPath.section. This is also not very readable. If a new member/someone outside tried reading this, it would be very confusing.

Let's make this better

Let's see how we can fix this issue and make it more readable.

For this example I will be using an example from Ithaca Transit.

We can start by creating a new enum type:

struct Section {
    let type: SectionType
    var items: [ItemType]
}

enum SectionType {
    case recentSearches
    case favorites
    case seeAllStops
}

enum ItemType {
    case busStop(BusStopModel)
    case placeResult(PlaceResultModel)
    case seeAllStops
}

Let us break this down. We have 3 new eum types.

ItemType

This enum defines the type of items within each section. For Ithaca Transit, search results contain both Bus Stops and Place Results (Google Places) mixed within one section. Creating this enum will allow having one section with multiple cell types.

SectionType

This defines the type of sections. With this example, we will have 3 sections. RecentSearches, favorites, and seeAllStops. This is pretty simple.

Section

Finally, we have an enum type that combines both ItemType and SectionType. It defines which type of section it is, and what items go in that section.

Implementation

Now let's use this in an example.

class SearchResultsViewController: UIViewController {

    var sections: [Section]! //we start out by declaring an array that will hold all of our sections

    override func viewDidLoad() {
        super.viewDidLoad()

        //create sections
        let recentSearches: [ItemType] = ... //get recent searches from network request or userdefaults etc. We also need to make sure we convert them into ItemType. This is pretty self explanatory.
        //The cool part here is recentSearches has both .busStop and .placeResult types.
        let recentSearchesSection = Section(type: .recentSearches, items: recentSearches)

        let favorites: [ItemType] = ... //get favorties
        let favortiesSection = Section(type: .favorites: items: favorites)

        let seeAllStopsSection = Section(type: .seeAllStops, items: [.seeAllStops])

        sections = [recentSearchesSection, favoritesSection, seeAllStopsSection]

        tableView.reloadData()
    }

    //Now lets fix the original issue
    // MARK: - TABLE VIEW
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var itemType: ItemType!

        let section = sections[indexPath.section]

        switch section.type {
            case .recentSearches, .favorites, .seeAllStops {
                itemType = section.items[indexPath.row] //this will give us the itemType for this specific cell
            }
        }

        switch itemType {
            case .busStop(let busStopModel):
                let cell = tableView.dequeue... as! BusStopCell
                //do set up
                return cell
            case .placeResult(let placeResultModel):
                let cell = tableView.dequeue... as! PlaceResultCell
                //do set up
                return cell
            case .sellAllStops:
                let cell = tableView.dequeue... as! SeeAllStopsCell
                //do set up
                return cell
        }
    }
}

Woohoo, now as you can see this is much simpler. We have defined sections and this makes it a lot more readable. You can even have a helper function that takes in an itemType and returns a cell to clean up this code even more, but I'll leave that to you.

If you add a new section, you'll get errors telling you you are missing a case statement, which will make sure you keep your code safe and free of bugs.

Last updated