Blog

Functional Snippet #22: Typed Table View Controllers Redux

In our previous snippet, we looked at typed table view controllers. This snippet tries to achieve the exact same thing, but with more code. When preparing the previous snippet, we started with a working code base. However, just before publishing, we realized that we wanted to try to make things simpler. After a lot of refactoring the code still compiled fine, but unfortunately, the previous snippet will crash at runtime. This snippet achieves the same thing, but with more code, and without a crash. Let’s get started.

The problem with the last snippet is that it’s currently impossible to make a generic subclass of a non-generic class. Therefore, we will create a non-generic subclass, and write a wrapper function that helps with type-safety. Our custom table view controller has two variables: one that holds the items, and one to configure the cells, given an item. The necessary data source methods are simple to implement, and work just like in the previous snippet.

class MyTableViewController: UITableViewController {
    var items: [Any] = []
    var configureCell: (UITableViewCell, Any) -> () = { _ in }

To create a wrapper around this non-generic class, we start off by creating a generic function. It takes two parameters: a list of items, and a way to configure a cell, given an item. Let’s start with the type.

func tableViewController<A>(items: [A],
                            configure: (UITableViewCell, A) -> ())
                         -> UITableViewController {

Because we need to work with an Any array, we need to box up all the items so that they have the same size.

    var vc = MyTableViewController(style: UITableViewStyle.Plain)
    vc.items = items.map { Box($0) }

In the configure cell function, we check if the value given by the MyTableViewController class has the right type, and pass it on to our typed configure function:

    vc.configureCell = { cell, obj in
        if let value = obj as? Box<A> {
            configure(cell, value.unbox)
        }
    }
    return vc
}

Now, creating a simple table view controller that displays an array of numbers is just one function call:

let rootViewController = tableViewController([1,2,3]) { cell, num in
    cell.textLabel?.text = "Cell \(num)"
}

If we want to instead show a table view with all the files in a given directory, we can change our code like this:

let fm = NSFileManager.defaultManager().
if let files = fm.contentsOfDirectoryAtPath(path, error: nil) as? [String] ?? []
let rootViewController = tableViewController(files) { cell, file in
    cell.textLabel?.text = file
}

Despite that this approach takes a little more work, we have still achieved a nicely typed API for an existing Objective-C class that was loosely typed. The full code is available as a gist.

Back to the Blog

recent posts