Swift: Using Complex Enums To Improve TableViews

One of the things that puzzled me about Swift when it came out was that you could put functions in an enum! That seemed like a pretty crazy pattern. I’m used to thinking of enums exactly as that – enums where I can specify values that are mapped into number. That’s it!

However, as I’ve been working on a few simple Swift TableViews and CollectionViews this week, I fell in love with how simple the complex Swift enum can make things. I’d like to share the pattern I’ve been experimenting with, and I’d love to hear what you think about it in the comments.

I’m going to build a very simple table view of Minions (from Despicable Me). Each cell in the table view will only have the image and name of the relevant minion:

minions table view

I built my enum as more of a data object:

enum MinionIndex: Int {
        case DAVE, BOB, JERRY, JORGE, KEVIN, MARK, PHIL, STUART, TIM
        
        static let minionNames = [
            DAVE : "Dave", BOB : "Bob", JERRY : "Jerry",
            JORGE : "Jorge", KEVIN : "Kevin", MARK : "Mark",
            PHIL : "Phil", STUART : "Stuart", TIM : "Tim"]
        
        func minionName() -> String {
            if let minionName = MinionIndex.minionNames[self] {
                return minionName
            } else {
                return "Minion"
            }
        }
        
        func minionImage() -> UIImage {
            return UIImage(named: "Minion\(minionName())")
        }
    }

Besides just mapping a minion to an int, there is a static dictionary that maps the minion’s index to a minion’s name, a function that gets the minion’s name from that static dictionary, and a function that returns the minion’s image.

This makes my tableView:cellForRowAtIndexPath: method very simple:

override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
        let cell = tableView.dequeueReusableCellWithIdentifier(minionCellIdentifier) as UITableViewCell
        
        if let minionIndex = MinionIndex.fromRaw(indexPath.row) {
            cell.textLabel.text = minionIndex.minionName()
            cell.imageView.image = minionIndex.minionImage()
        }
        
        return cell
    }

Notice how the data – index, name, image of the minion – stays together in the enum. In Objective-C, I would have had an enum, an array of minion names, and likely a function to convert minion names into an image, or worse – I could have used Switch statements to make a much longer tableView:cellForRowAtIndexPath: method. In fact, my coworker @louielouie actually calls this the “anti-switch-statement pattern”.

Overall, I’ve been enjoying using this pattern for my new iOS 8 Swift TableViews.

Let me know what you think!

You can view the full source code on Github here.

Enjoy the article? Join over 14,500+ Swift developers and enthusiasts who get my weekly updates.

  • frytaz1

    Great idea

  • dr.obvous

    I am sorry but in Objective-C,
    This thing would be written as plist in file not as a enum.
    and used KVC to map to TableView.

    Problem is that most current obj-c programmers
    are really c++ and they bring that kind of experience
    to iOS and will transfer to Swift.

  • Karl

    A really practical solution! I’ll test this on my own. Thank you for the great idea!

  • Mike

    Thanks for the article. Is there a way to get the number of minions dynamically instead of hard-coding the number? So if you add another minion, you don’t have to also remember to update the count that gets returned from numberOfRowsInSection?

  • Davide De Franceschi

    Why did you use an Int raw value for that enum?

    • So it matches my indexPath.row

      • Davide De Franceschi

        It feels a bit forced. Raw values should be used for compatibility or when they really represent the enum value, and since a minion isn’t (imho) really represented by its index (they could change index or they could even have gaps between their indices) a different approach should be preferred.

        Ways that come to mind are a “minionForTableIndex(index: Int) -> Minion” static method of the enum or a “allMinions() -> [Minion]”, still static, and convert to an index.

  • Glenn Sugden

    Found this DuckDuckGo’ing for something else about enums, but thought this was a good exercise. 🙂

    Maybe (a bit cleaner / clearer? Well .. if this text box didn’t kill the spacing -heh-):

    //: Playground – noun: a place where people can play

    import UIKit

    enum Minion:Int

    {

    case DAVE, BOB, JERRY, JORGE, KEVIN, PHIL, STUART, TIM

    func name() -> String

    {

    switch self

    {

    case DAVE:

    return “Dave”

    case BOB:

    return “Bob”

    case JERRY:

    return “Jerry”

    case JORGE:

    return “Jorge”

    case KEVIN:

    return “Kevin”

    case KEVIN:

    return “Kevin”

    case PHIL:

    return “Phil”

    case STUART:

    return “Stuart”

    case TIM:

    return “Time”

    }

    }

    func image() -> UIImage?

    {

    println(“Loading image: Minion(self.name())”)

    return UIImage(named: “Minion(self.name())”)

    }

    }

    let daveMinion = Minion.DAVE

    println(daveMinion.name())

    println(daveMinion.rawValue)

    let image = daveMinion.image()

    let shouldBeBobMinion = Minion(rawValue: 1)

    // TableView example becomes:

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

    let cell = tableView.dequeueReusableCellWithIdentifier(minionCellIdentifier) as UITableViewCell

    if let minion = Minion(rawValue: indexPath.row) {

    cell.textLabel.text = minion.name()

    cell.imageView.image = minion.image()

    }

    return cell

    }