Today I would like to discuss how to present UITableViewCell contents by using enums. A simple switch statement can help us with displaying data on our tableView. What scenario would I like to focus on?

PlaceInfoView

To be more precise, I have created a simple schema of this screen:

PlaceInfoScheme

By the way, have you had a chance to use Sketch for prototyping? Quite a nice tool!!!

The question is: what is the best proposal of enum configurations and functions, which can help us with displaying data when using tableView:cellForRowAtIndexPath: method for an example above?

My first proposal was:

enum PlaceInformation: Int { // Used on UISegmentControl
    case General
    case More
}

enum GeneralInfo: Int {//Cells for General Info Segment (Tab)
    case Description
    case HowToGoHere
    case Hotel
}

enum MoreInfo: Int {//Cells for More Info Segment (Tab)
    case CurrentWheather
    case InterestingPlacesNearby
}

class PlaceInformationViewController: UIViewController, UITableViewDataSource {
    
    var tableView: UITableView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView = UITableView()
        ...
    }

    let selectedTabIndex = 0 //selected segment on UISegmentedControl
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = UITableViewCell()
        let row = indexPath.row
        let selectedInformationTab: PlaceInformation = PlaceInformation(rawValue: selectedTabIndex)!
        
        switch selectedInformationTab {
        case .General:
            guard let informationCellType = GeneralInfo(rawValue: row) else { break }
            switch informationCellType {
            case .Description:
                print("Setup Description Cell")
            case .HowToGoHere:
                print("Setup HowToGoHere Cell")
            case .Hotel:
                print("Setup Hotel Cell")
            }
        case .More:
            guard let informationCellType = MoreInfo(rawValue: row) else {  break }
            
            switch informationCellType {
            case .CurrentWheather:
                print("Setup Wheather Cell")
            case .InterestingPlacesNearby:
                print("Setup InterestingPlacesNearby Cell")
            }
        }
        return cell
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
}

Some statistics of the above code:

  • Lines of code of tableView:cellForRowAtIndexPath: function: 28
  • What did SwiftLint say?

SwiftLint

  • Codebeat rated the code to 3.97 GPA (Function too long)

My intuition prompted that it can be done better:). So I have started thinking of how to refactor the code.

First refactoring

So here we go with first refactoring. Below is my to do list to make this enum better:

  • Remove white spaces according to SwiftLint
  • Reduce cases in enum to one line
  • Use nested enums
  • Necessarily I wanted to check if enum associated values can improve code metrics. I don’t know why, but I hoped that it will improve my situation:)
enum PlaceInformationCellModel {
    // returned cell depends on selectedTab and tableview section
    case Header(selectedTab: PlaceInformation, section: Int)

    enum PlaceInformation: Int { // Used on UISegmentControl
        case General
        case More
    }
    
    enum PlaceHeader {
        case Description, HowToGoHere, Hotel, CurrentWheather, InterestingPlacesNearby
        static let allGeneralHeaders = [Description, HowToGoHere, Hotel]
        static let allMoreHeaders = [CurrentWheather, InterestingPlacesNearby]
    }

    var value: PlaceHeader {
        switch self {
        case .Header(let tab, let sectionIndex):
            switch tab {
            case .General:
                return PlaceHeader.allGeneralHeaders[sectionIndex]
            case .More:
                return PlaceHeader.allMoreHeaders[sectionIndex]
            }
        }
    }
}

class PlaceInformationViewController: UIViewController, UITableViewDataSource {

    var tableView: UITableView?

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView = UITableView()
        ...
    }
    
    let selectedTabIndex = 1 //selected segment on UISegmentedControl

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
        let cellModel: PlaceInformationCellModel = PlaceInformationCellModel.Header(selectedTab: PlaceInformationCellModel.PlaceInformation(rawValue: selectedTabIndex)!, section: indexPath.row)
        let header = cellModel.value
        let cell = UITableViewCell()

            switch header {
            case PlaceInformationCellModel.PlaceHeader.Description:
                print("Setup Description Cell")
            case PlaceInformationCellModel.PlaceHeader.HowToGoHere:
                print("Setup HowToGoHere Cell")
            case PlaceInformationCellModel.PlaceHeader.Hotel:
                print("Setup Hotel Cell")
            case PlaceInformationCellModel.PlaceHeader.CurrentWheather:
                print("Setup CurrentWheather Cell")
            case PlaceInformationCellModel.PlaceHeader.InterestingPlacesNearby:
                print("Setup InterestingPlacesNearby Cell")
            }
        return cell
    }
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
}

Is it better? I hoped so, so I have asked my friends about feedback:

  • Lines of code of tableView:cellForRowAtIndexPath: function: 20

  • What did SwiftLint say?

SwiftLint

  • What did codebeat say?: 4.00 GPA

Second refactoring

I still had a feeling that the code was too complicated. I thought that getting rid of associated values would make it simpler.

enum PlaceInformation {

    case General, More

    enum PlaceHeader: Int {
        case Description, HowToGoHere, Hotel, CurrentWheather, InterestingPlacesNearby
        static let allGeneralHeaders = [Description, HowToGoHere, Hotel]
        static let allMoreHeraders = [CurrentWheather, InterestingPlacesNearby]
    }

    var placeValues: [PlaceHeader] {
        switch self {
        case .General:
            return PlaceHeader.allGeneralHeaders
        case .More:
            return PlaceHeader.allMoreHeraders
        }
    }
}

class PlaceInformationViewController: UIViewController, UITableViewDataSource {

    var tableView: UITableView?

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView = UITableView()
    }
    
   	let selectedInformationTab: PlaceInformation = .General

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let header = selectedInformationTab.placeValues[indexPath.row]
        let cell = UITableViewCell()

        switch header {
        case .Description:
             print("Setup Description Cell")
        case .HowToGoHere:
            print("Setup HowToGoHere Cell")
        case .Hotel:
            print("Setup Hotel Cell")
        case .CurrentWheather:
            print("Setup CurrentWheather Cell")
        case .InterestingPlacesNearby:
            print("Setup InterestingPlacesNearby Cell")
        }
        return cell
    }
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
}

Feedback:

  • Lines of code of tableView:cellForRowAtIndexPath: function: 18
  • SwiftLint:

SwiftLint

  • Codebeat gave me 4.00 GPA for this code. codebeat badge

It seems that the easiest solution is the best:

GitHub Logo

Third refactoring

I would like to leave the next iteration of refactoring for you. What would you change to improve the solution?

Resources