Intro to UITableView in Swift

UITableView is the bread and butter of iOS development. And for good reason: it’s a great way to represent array data on a mobile device. You’ll find yourself using UITableView quite often.

Don’t be intimidated by method names like cellForRowAtIndexPath and dequeueReusableCellWithIdentifier. Soon they’ll roll off your tongue like JohnJacobJingleheimerSchmidt.

Also pay attention to iOS patterns like Delegate and DataSource. You can apply what you learn here toward Collection Views, Page Views, Map Views, and pretty much every other piece of UI in iOS.

I’m going to purposely run into errors along the way to help you understand what problem each piece of code is trying to solve. You’ll get to see some common issues and how to get past them. As the saying goes, “an expert is someone who has made all the mistakes which can be made.”

There’s also a lot of animated GIFs, just in case you missed them from the ‘90s. More is caught than taught, and in these videos I’ll try to capture the thought process of figuring out the answer.

Since it’s an intro tutorial, we’ll take things slow.

Setup the Project

We want to keep our project simple so that we can focus on the TableView itself.

Create a new Project, choosing Single View Application in Swift

Disable Size Classes to give us more elbow room in the Storyboard. Do this by selecting the File Inspector and unchecking Use Size Classes

Set the Table

TableView setup begins in the Storyboard. First we’ll position the Table View in a good spot, and then weld it into place.

Drag a Table View into the View Controller. If you hover over the View Controller for a second, the edges will try to line up on their own.

Click the Add New Constraints icon (the Tie Fighter, not the Tie Advanced)

Uncheck Constrain to Margins

Set constants in all directions to zero by clicking the orange beams

Change Update Frames to Items of New Constraints. This syncs the screen display to the constraints you added.

Click Add 4 Constraints

Note: if the table goes somewhere crazy, just Undo (⌘Z). The Undo only applies to the table repositioning, but your constraints are still there.

Now we’re going to wire up the DataSource. (More on what this means later)

Control-drag from the TableView to the View Controller’s Scene icon (it’s the yellow one on the top left).

Choose the dataSource Outlet

While we’re here, let’s drag in a Table View Cell into the Table View.

It’s a good idea to run your project occasionally. Use the keyboard shortcut ⌘R. It will crash, thanks to the DataSource step.

What is a DataSource?

Surgical techs hand the surgeon various implements during surgery. Similarly, the DataSource hands the TableView a cell whenever it asks for a particular row.

In the previous step, we wired the View Controller as the DataSource. So we had better make sure View Controller is up to the task. Outside of the ViewController class, add the following:

extension ViewController : UITableViewDataSource {
}

We’re saying that ViewController can perform the duties of the UITableViewDataSource protocol. Notice the use of extension. This is just a code organization trick to group protocol methods together.

You’ll notice we’re getting a new error:

Type ‘ViewController’ does not conform to protocol ‘UITableViewDataSource’

UITableViewDataSource has two required methods that we haven’t implemented. Fortunately, the documentation includes boilerplate code that we can just copy. ⌘-click the word UITableViewDataSource and copy in any method that doesn’t say “optional.” In this case, it’s the first two.

numberOfRowsInSection gets called any time the table wants to know how many rows it needs.

cellForRowAtIndexPath gets called each time the table asks for a particular row.

You’ll notice we now have a different error (don’t worry, a different error is a sign of progress):

Missing return in a function expected to return…

The method signatures each have an arrow (->) indicating an expected return value. So let’s give these methods what they want.

numberOfRowsInSection is asking for an Int, so hardcode 1 row for now.

cellForRowAtIndexPath is asking for an UITableViewCell. We’re going to use a method called dequeueReusableCellWithIdentifier. Instead of creating a new cell every time, we “reuse” a cell (much like rinsing out a dirty cup and handing it to your guest). This is a common pattern that you’ll see in collection views and map annotations as well. Why create new, when you can dequeue?

Now that we’ve taken care of all the errors and warnings, build & run. Again, it should crash. (Just be glad I’m not teaching driver’s ed.)

We told the TableView to look for an UITableViewCell on the Storyboard with a Reuse Identifier of “cell.” Let’s fix this by selecting the Table View Cell, and on the Attributes Inspector change the Identifier to cell. Tip: you have to hit Enter to apply your change.

Build and Run. It should actually run this time without crashing. Congratulations, we now have a blank tableview.

Now we’re cooking with gas

Now that the table is set, it’s time to eat! Let’s start populating the table rows. Add some more rows by updating numberOfRowsInSection to 10. And let’s have this row number show up in the cell. UITableViewCell has a built in UILabel called textLabel. You can set the text to indexPath.row, which is just the row number.

Note: these images are being loaded synchronously, which is bad. There’s a much better way, but that’s for another tutorial. If you feel the need to fix this, there are tutorials online for downloading images asynchronously.

Using Custom Cells

The vanilla UITableViewCell is okay, but most projects will have custom UI. We’re going to put the image in a circle, nothing fancy. The goal is to subclass UITableViewCell with a custom class, and wire the image.

First, create a new Swift File (⌘N) and name it something like TsumCell. Then subclass UITableViewCell.

import UIKit
class TsumCell : UITableViewCell {
}

In the Storyboard, set the cell’s Class to the one we just created. You can do this on the Identity Inspector.

Now add some custom UI to the prototype cell.

Drag to resize the cell height to 60

Put an ImageView in the left side. Resize it so that it’s approximately a square.

Place a Label next the image and use the blue guidelines to center it vertically

Now add some constraints to these elements.

Add top, left, and bottom padding to the image. 5 pixels should be good.

Don’t forget to uncheck Constrain to margins

Check the Aspect Ratio box

Control-drag from the Label to the Image View. ⌘-select both Horizontal Spacing and Center-Y

Control-drag from the Label to the empty space on the right, and select Trailing Space to Container Margin

If you’re feeling lucky, change Update Frames to Items of New Constraints

The constraints don’t always come out right the first time, so use the Size Inspector to tweak the constants.

Now wire up the image and label. Alt-click TsumCell.swift to open it alongside the Storyboard. Control-drag from the Image View to the TsumCell class and create an Outlet. Name it dwarfFace.

Do the same for the Label as well, but call it dwarfName.

Note: the outlets need to be made to TsumCell rather than View Controller.

Now set the cell background to a banana yellow, like Snow White’s dress. You can do this from the Attributes Inspector by choosing a Custom color for the Background.

Now let’s go back to our View Controller and update our cellForRowAtIndexPath method to use our custom cell. Change UITableViewCell to TsumCell (remember we set the custom class in the Storyboard).

Build and Run. Great, now after all that work, it looks pretty much like what we had before. But we did lay the groundwork for future customization. Let’s try to make the image circular, which is common for user profile avatars.

In TsumCell, change the cornerRadius to make the image round. You’ll need to set clipsToBounds as well. Use the method layoutSubviews() which is kind of like viewDidLoad(), but for cells.

Build and Run. Looks pretty cute, but what’s with all these lines? For now, let’s patch up this UI issue by setting UITableViewAutomaticDimension on the TableView. Before we do so, we need to wire up the TableView to the View Controller.

Alt-click on ViewController.swift to open it alongside the Storyboard. Control-drag from the TableView to create an outlet. Call it tableView.

Within viewDidLoad, set the rowHeight to automatic dimension. It should look something like this:

Build and Run. The lines look under control, but now the circles aren’t quite round anymore. This is good enough for now, but there are tutorials online if you want to resolve the row height issue.

Recap

From a blank project, we dropped a TableView onto the Storyboard. We wired up the DataSource, and populated the cell with a hard-coded image and an array of text. Then we subclassed UITableViewCell and wired outlets to some custom UI. This allowed us to do something like clip the image to a circle. This should give you a good starting point to play around with default cell types, and create some custom cells of your own.