In this talk, we’ll look at how we can work with table view controllers in a more Swifty way. We’ll use generics, structs and functions to create a reusable subclass of UITableViewController
.
Table View Controllers in Swift (0:00)
Hi everyone, here we will implement a table view controller and do three things with it. First, we will make it generic, then add undo functionality to editing it, and finally pull out a complex initializer into a configuration struct.
Basic Setup (0:44)
final class MyTableViewController: UITableViewController {
let items: [Person]
init(items: [Person], style: UITableViewStyle) {
self.items = items
super.init(style:style)
}
}
Let’s a create a table view controller. I have an empty one and the first thing we’re going to do is store an array in there to display. We’re going to create an array items
which will hold Person
s. Now we need to create an initializer for this as well.
The initializer gets the array as well. We get in the items, and the style - UITableViewStyle
. Next we need to set those properties. First we need to set the items and only then we can call super. Now, here we need to pass on the items as well.
There’s one more thing to do. The table view wants an extra initializer, and we can press the magic shortcut. Control, Option, Command, F. It means “Fix All Issues.” It’s very useful.
We’re not displaying the cells yet. Hopefully everybody knows how to do it, we’ll do it together. The first thing is that we need to implement tableView(_:numberOfRowsInSection:)
.
Next, add tableView(_:cellForRowAtIndexPath:)
and configure the cell.
Configuring the Cell (3:41)
What if we want to use the subtitle style? We can make this a configuration option as well called cell style. We add a property here, and pass it in in the initializer.
What if we want to change the way we configure it? For example now we want to set the detail text label. Instead of changing it in the cellForRowAtIndexPath
, again we can make this a configuration option. There are two ways to do it. One would be to use protocols, but if we want more flexibility we can use functions as well.
If you are used to object-oriented programming, then you might want to do something like adding a method to configure the cell, and pass in the cell and the item. Then other people can subclass.
However, I like to make my classes final. As you can see here. So how do we do this? Well instead of a method we can pass this in as a parameter. So configure cell is a function that given a cell and an item, configures it.
final class MyTableViewController: UITableViewController {
let items: [Person]
let configureCell: (cell: UITableViewCell, item: Person) -> ()
init(items: [Person], style: UITableViewStyle, configureCell: (UITableViewCell, Person) -> ()) {
self.items = items
self.configureCell = configureCell
super.init(style:style)
}
}
We need to pass this is in as a parameter as well. Configure cell is a function that takes a cell and a person and configures it.
When we create our table view, we can add a trailing closure. Given the cell and the item, we can say cell.text, label.text, and do the same for the detail text label.
let tableVC = MyTableViewController(items: people, style: .Plain) { cell, item in
cell.textLabel?.text = item.name
cell.detailTextLabel?.text = item.city
}
Generic Model (7:12)
What if we want to have a table view controller that shows Pokemon instead? This is where we can use generics to abstract this Person
out. So lets do that.
We’re going to add a generic parameter item. Now we can replace every person occurrence with item. This is a very mechanical process and often when you’re writing generic classes it can be very mechanical. You can see that everything still works.
Next let’s create a Pokedex view controller. So we can pass in the Pokedex, we can use the group style, default, and in our configure cell we get the cell and item and then we said cell.textLabel.text
is item.
Functional Undo (9:08)
Let’s add editing functionality and undo functionality. Again we’re going to make this a parameter of our initializer which is starting to get complex. Editable is a bool, and then below the initializer, we can create a button for that. If editable
, then we can create a navigation bar button item.
Our edit function takes a sender which is AnyObject
and is going to toggle the editing.
Now we can click Edit and we see edit mode. To implement the editing we’re only going to implement deletion. We can override tableView(_:commitEditingStyle:forRowAtIndexPath:)
. We check if we’re deleting. Otherwise we return and now we can remove from the items.
After that, we need to make sure that we updated the table after we change our model. I’m going to take a shortcut and reload the entire table view. So we can use the change. Now we can actually delete.
Up next, to add the undo functionality, we’re going to create an undo struct for this. It’s going to be generic as well, and we need to initialize it with an initial value which is an array of items. In the initializer we just set it.
struct UndoHistory<Item> {
let initialValue: [Item]
let history: [[Item]] = []
init(_ initialValue: [Item]) {
self.initialValue = initialValue
}
}
How do we keep track of the history? Well we can simply use an array of an array of items for that. We start with an empty one, and every time we change something, we push onto this array.
var currentValue: [Item] {
return history.last ?? initialValue
}
Now that we have this we can define a property current value, which is an array of items, and we’ll just check for the last item in the history and if it’s null then we use the initial value.
To use our undo history struct, we add it as a property to the table view controller, and every time we access item we just go to the undo history’s current value.
The first thing for deletion is to add a setter for current value.
var currentValue: [Item] {
get { return history.last ?? initialValue }
set { history.append(newValue) }
}
The setter just pushes onto the array. We also need to add a setter for items.
Finally, to make deletion work, we need to make sure that when the history changes that we reload our table view.
Up next, let’s add the undo part. First we’ll add a function undo. It removes an item from the history. This function needs to be marked as mutating because it changes the struct. Now we need to call it so we need to create a button for that. So we create again a UIBarButtonItem
, Undo, target is self and the selector is undo.
mutating func undo {
history.popLast()
}
We need to implement the selector. In it, we just call history.undo. So we delete Ash, we delete Natasha, and then we can undo. The reason this works is because arrays are structs, which is really, really cool.
Configuration Struct (18:59)
The next thing we’ll do is make our table view class a little bit simpler. To do that first we’re going to make it more complicated. We’re going to take all the initializer parameters and put them into a struct. I’m going to cut it out and create a new struct called TableViewConfiguration
. It also needs to be generic. And then we need to make these properties. So it’s a lot of typing. Finally you configure cell.
Now to use this, we are now going to pass in a table view configuration into the view controller’s initializer. And now we have to use configuration.items everywhere. configuration.cellStyle, and configuration.editable.
This makes our code much nicer because we can stick all of our configuration into a variable. If you think about testability this is great. This is a simple struct, and we can easily test that it’s correct. You can reuse this everywhere, and you only need to test your table view controller once.
What you can also do is change it a little bit. So lets imagine that this variable is in a library. What we can now do is change it in our app. So because it’s a struct we can easily make a copy. And then we set the config.style to grouped, and now we can use it.
This is a very powerful technique that I use over and over again. I make a class with a complicated initializer and then pull out everything out of the initializer into a struct. I get very reusable and testable code.
What I want you to do is to think about if you can use this in your code and if you can use these patterns in your codebase. Specifically, see if you can use generics and structs to clean up your code; it’s going to be very beneficial. If you want to play around with this, what I would encourage you is to add moving functionality. You will bump into a limitation with the undo struct but I’m also sure that you will figure that out and fix it. Thank you.
Q&A (22:57)
Q: What are some other examples where you’ve used this pattern of just generalization and pulling out configuration?
Chris: My favorite example is to do this for networking. If I started with just a function that loads some data from the network, such as prices as JSON and converts it to person values. Then I pulled that out and ended up with a very simple struct that describes the endpoints of your API. That struct was very easily testable because it’s just the struct and all the asynchronous code was hidden in one generic place.
I wrote a blog post about this called Tiny Networking but this was a long time ago. It probably doesn’t compile anymore but that gives you something to play around with.
Q: What was the keyboard shortcuts that you used to fix the fixable errors?
Chris: Control + Option + Command + F.
Check it out in the Editor menu, along with other goodies such as indentation and navigating between errors.
Q: How would you use a pattern like this with Xcode’s Storyboards?
Chris: We once used Storyboards and segues all over the place for a Mac application, and I liked it. It is possible to use this with Storyboards by creating a subclass of your generic table view controller class that is not generic.
Nevertheless, I stopped using Storyboards more and more and I really like this moving all the logic outside of the view controller. You view controller can then be very dumb and you can do all the navigation outside of your view controller which makes it way more reusable.
The benefit of that outweighs the benefit of using Storyboards. I hope in the future it will become easier to mix and match. Anything we can do to make our view controllers dumber and more reusable is great.
Receive news and updates from Realm straight to your inbox