Structuring UITableViews by Using Enums

UITableViews

UITableViews are obviously one of the most used user interface elements on iOS. However, if you are dealing with UITableViews, that have a lot of different sections, your code can become messy quickly. Even worse, it will become very difficult to change the implementation of the UITableView later on. But you can structure UITableViews by using enums.

Hint: This post is using Swift 3, Xcode 8 and iOS 10

So imagine we have an UITableView that has three different kinds of sections. Each section also has another type of table view cell.

I like to create a separate class for the data source of the table view because otherwise the view controller tends to become massive. You can learn more about this topic in the following post: Outsource your UITableViewDataSource!. So, without using enums, a typical data source would look like this:

import UIKit

let tableViewCell1Identifier = "tableViewCell1Identifier"
let tableViewCell2Identifier = "tableViewCell2Identifier"
let tableViewCell3Identifier = "tableViewCell3Identifier"

class TableViewDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
    
    func registerTableViewCellIn(tableView: UITableView) {
        
        tableView.register(UINib(nibName: "TableViewCellType1", bundle: nil), forCellReuseIdentifier: tableViewCell1Identifier)
        tableView.register(UINib(nibName: "TableViewCellType2", bundle: nil), forCellReuseIdentifier: tableViewCell2Identifier)
        tableView.register(UINib(nibName: "TableViewCellType3", bundle: nil), forCellReuseIdentifier: tableViewCell3Identifier)
        
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 3
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0 {
            return 4
        } else if section == 1 {
            return 8
        } else if section == 2 {
            return 2
        }
        return 0
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        var tableViewCell: UITableViewCell
        
        if indexPath.section == 0 {
            let type1Cell = tableView.dequeueReusableCell(withIdentifier: tableViewCell1Identifier) as! TableViewCellType1
            type1Cell.type1Label.text = "Type 1, Row \(indexPath.row)"
            tableViewCell = type1Cell
        } else if indexPath.section == 1 {
            let type2Cell = tableView.dequeueReusableCell(withIdentifier:
                tableViewCell2Identifier) as! TableViewCellType2
            type2Cell.type2Label.text = "Type 2, Row \(indexPath.row)"
            tableViewCell = type2Cell
        } else {
            let type3Cell = tableView.dequeueReusableCell(withIdentifier: tableViewCell3Identifier) as! TableViewCellType3
            type3Cell.type3Label.text = "Type 3, Row \(indexPath.row)"
            tableViewCell = type3Cell
        }
        
        return tableViewCell
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        
        if section == 0 {
            return "Section Type 1"
        } else if section == 1 {
            return "Section Type 2"
        } else if section == 2 {
            return "Section Type 3"
        }
        
        return ""
    }
    
}

But what happens if we want to exchange section one and section three? We had to change the code at several positions, at least in the following methods:

  • func numberOfSections(in tableView: UITableView) -> Int
  • func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
  • func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
  • func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?

And this is just a small example app. In a real app there are even more methods that we need to change. And if we want to decide the order of the sections at runtime, it would become very tricky. So this approach is a bad one.

Instead, let us define an enum for the section types:

enum TableViewSectionTypes {
    
    case SectionType1
    case SectionType2
    case SectionType3
    
}

Then we can define the sections in a property:

let sections = [TableViewSectionTypes.SectionType2,TableViewSectionTypes.SectionType1,TableViewSectionTypes.SectionType3]

Now we can decide dynamically what happens in the datasource and delegate methods:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let sectionType = sections[section]
        
        switch sectionType {
        case .SectionType1:
            return 4
        case .SectionType2:
            return 8
        case .SectionType3:
            return 2
        }
        
    }

If we now want to change the order of the sections, we just have to adjust the sections array. And that’s just ONE place in the code – compared to four places in our previous example.

Even more, it would be very easy to construct the sections property at runtime.

Here’s how the whole data source object looks like:

import UIKit

enum TableViewSectionTypes {
    
    case SectionType1
    case SectionType2
    case SectionType3
    
}

let tableViewCell1Identifier = "tableViewCell1Identifier"
let tableViewCell2Identifier = "tableViewCell2Identifier"
let tableViewCell3Identifier = "tableViewCell3Identifier"

class TableViewDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {
    
    let sections = [TableViewSectionTypes.SectionType2,TableViewSectionTypes.SectionType1,TableViewSectionTypes.SectionType3]
    
    func registerTableViewCellIn(tableView: UITableView) {
        
        tableView.register(UINib(nibName: "TableViewCellType1", bundle: nil), forCellReuseIdentifier: tableViewCell1Identifier)
        tableView.register(UINib(nibName: "TableViewCellType2", bundle: nil), forCellReuseIdentifier: tableViewCell2Identifier)
        tableView.register(UINib(nibName: "TableViewCellType3", bundle: nil), forCellReuseIdentifier: tableViewCell3Identifier)
        
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let sectionType = sections[section]
        
        switch sectionType {
        case .SectionType1:
            return 4
        case .SectionType2:
            return 8
        case .SectionType3:
            return 2
        }
        
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let sectionType = sections[indexPath.section]
        
        var tableViewCell: UITableViewCell
        
        switch sectionType {
        case .SectionType1:
            let type1Cell = tableView.dequeueReusableCell(withIdentifier: tableViewCell1Identifier) as! TableViewCellType1
            type1Cell.type1Label.text = "Type 1, Row \(indexPath.row)"
            tableViewCell = type1Cell
        case .SectionType2:
            let type2Cell = tableView.dequeueReusableCell(withIdentifier:
                tableViewCell2Identifier) as! TableViewCellType2
            type2Cell.type2Label.text = "Type 2, Row \(indexPath.row)"
            tableViewCell = type2Cell
        case .SectionType3:
            let type3Cell = tableView.dequeueReusableCell(withIdentifier: tableViewCell3Identifier) as! TableViewCellType3
            type3Cell.type3Label.text = "Type 3, Row \(indexPath.row)"
            tableViewCell = type3Cell
        }
        
        return tableViewCell
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        let sectionType = sections[section]
        
        switch sectionType {
        case .SectionType1:
            return "Section 1 Type"
        case .SectionType2:
            return "Section 2 Type"
        case .SectionType3:
            return "Section 3 Type"
        }
    }
    
    
}

Do you like this approach? Please comment down below!

References

Image: @ Erce / shutterstock.com

2 comments

  1. I think it’s a great start!. You can go further and setup enums with properties – cells, numbers of sections etc.

    Then just use

    switch sectionType {
    case .SectionType1:
    return Section1Type.numberOfRows

    switch sectionType {
    case .SectionType1:
    return Section1Type.titleForHeader

    and so one.
    This way your enum will keep all details about table. Easy to maintain and adjust.

Comments are closed.