Tuesday, February 7, 2017

Use constants for your dynamic UITableView sections

I started this blog as a way to post random thoughts on reverse-engineering and related stuff. However, I also do iOS development (Android development too, for that matter). Hence, this...short blog post.

Let's suppose we are writing a fictitious app about displaying pictures of dogs and cats, separated in two sections: one for the dogs, and the other one for the cats. A common pattern seen in code written in tutorials all over the web — and books...sigh — when creating dynamic table views, is having code similar to that shown below:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if section == 0 {
        return dogs.count
    } else if section == 1 {
        return cats.count
    } else {
        return 0
    }
}


This is only one method, which we use to determine how many rows each section will have. Now, we've decided that it would be cool if we displayed pictures of dogs in a custom UITableViewCell, and pictures of cats in another custom UITableViewCell:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if indexPath.section == 0 {
        let cell = tableView.dequeueReusableCell(withIdentifier: "doggoCell", for: indexPath) as! DogCell
        cell.dogImage = dogs[indexPath.row].image
        return cell
    } else { // indexPath.section == 1
        let cell = tableView.dequeueReusableCell(withIdentifier: "catCell", for: indexPath) as! CatCell
        cell.catImage = cats[indexPath.row].image
        return cell
    }
}

Alright, looking good. Now we decide that it's time to provide a custom title for each section:

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if section == 0 {
        return "No bad boys allowed"
    } else if section == 1 {
        return "We can haz cheezburgerz"
    }
    
    return nil
}

We deserve a pat on the back for the hard work, except that we need to do, ahem...one last thing. Somehow we decide that it would be a great idea to include a special section for owls. Don't get me wrong though, not just any type of owl. Superb owls only. As if that made any difference. But I have even better news: market research has shown that our app would have much greater success if we placed cats first, followed by owls, and lastly dogs (sorry dogs, nothing personal).

I hope that by now you understand that we've crafted our failure with our own hands. Hardcoding sections (and rows for that matter) in the various table view delegate and data source methods is woefully wrong. The solution to this problem is simple: declare constants for each section (or for rows, similarly). Doing this would make it easier for us in the future should the need arise to add some kind of other animal in our app:

let sectionDogs = 0
let sectionCats = 1

In such a case, our original titleForHeaderInSection method would look like this:

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if section == sectionDogs {
        return "No bad boys allowed"
    } else if section == sectionCats {
        return "We can haz cheezburgerz"
    }
    
    return nil
}

Not only would it be easier to the eye to understand which section you are setting up, but also the late minute changes described above (adding the owls) would be a breeze. Let me illustrate this only for the titleForHeaderInSection method:

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if section == sectionDogs {
        return "No bad boys allowed"
    } else if section == sectionCats {
        return "We can haz cheezburgerz"
    } else if section == sectionOwls {
        return "Don't call me, owl call you!"
    }
    
    return nil
}

And we would only need to add the sectionOwls constant, and also adjust the sectionDogs and sectionCats constants:

let sectionDogs = 2
let sectionCats = 0
let sectionOwls = 1

So by merely changing the values of each of the constants, we could easily reorder sections, without touching the data source and delegate methods.

I hope this was an interesting read, and I really hope that this will prevent any (further?) headache to future developers who are just starting out.