Lecture Handout

What is a UITableView?

A table view (UITableView) is a subclass of UIScrollView, which is a type of view that a user can scroll through. Simply put, the main purpose of a table view is to display information in the form of a list. A bit more specifically, a table view consists of sections where each section consists of 3 parts: a header, cells (you can just think of a cell as a row), and a footer.

Why are UITableViews Important?

UITableViews are an extremely powerful type of view and are used in a ton of successful apps today (i.e. a Spotify playlist, your Gmail inbox, Messenger messages). They provide an easy way to display a lot of information in a compact, organized manner.

UITableView Examples

Example 1: Clock Each alarm time is a UITableViewCell. Each cell has two labels (the time and the "Alarm" description) and a segmented control.

Example 2: Settings The entire Settings page is a UICollectionView, with several sections (each "group" of options between the large gray gaps). Each option is a UITableViewCell.

Using a UITableView

Setting Up a UITableView

Besides usual initialization process for initializing any UIView (calling the init function, set translatesAutoresizingMaskIntoConstraints to false, adding the view as a subview to parent view, and setting up constraints), there are 3 additional things that you should do to setup a UITableView:

  1. Set the tableView’s delegate

  2. Set the tableView’s dataSource

  3. Register your custom UITableViewCell class

We will discuss 1 and 2 in the next section. What does 3 mean? Basically, in order to use any custom cell class that we've created inside a tableView and be able to dequeue it later on, we need to let the tableView know by registering the cell class and associating it with an identifier (this can be any string).

Dequeuing Cells

What does it mean to dequeue a cell and why do we want to do it?

Well, imagine that we were using a table view to display a list of all the students at Cornell. This means that we would have thousands of cells, which would be really expensive in terms of performance and memory! Instead of creating a new cell for each student, as the user scrolls down the list and the cells near the top disappear from the screen, we reuse those cells to display the next set of cells. This way, we will only be creating 5 or 6 cells instead of thousands.

By registering a cell class and associating it with an identifier, all cells with that identifier will try to reuse another cell of that identifier so that the same type of cell can be used to render different content.

The following is an example of initializing a UITableView:

// Initialize tableView
tableView = UITableView(frame: .zero)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
// self is the view controller that holds this tableView 
tableView.register(CafeteriaTableViewCell.self, forCellReuseIdentifier: someReuseIdentifier)
// Then add tableView as a subview to parent view and setup constraints

Important Protocols: UITableViewDataSource & UITableViewDelegate

1. UITableViewDelegate

A UITableViewDelegate must conform to certain methods and can also provide some optional methods which allows the delegate to manage selections, configure section headings and footers, help to delete and reorder cells, and perform other actions. The one method that you should always implement is one we'll refer to as heightForRowAt:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 50
}

This function requires you to return a CGFloat representing the height (in this example, 50 pixels) that the cell at a certain row or section should have.

Another method that you'll commonly use is one we'll refer to as didSelectRowAt:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // Perform some action upon selecting a row
}

This function is what gets called whenever one of the cells in your table view is selected by the user. Thus, if you want to trigger some sort of action or animation upon selection, this is the place you would do it!

2. UITableViewDataSource

Similarly, a UITableViewDataSource must conform to certain methods. Unlike the UITableViewDelegate, however, the role of a UITableViewDataSource is to provide the table-view object with the information it needs to construct and modify a table view. The first method that you need to implement is one we'll refer to as cellForRowAt:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: customReuseIdentifier, for: indexPath) as! CustomCell
    let dataModel = dataModelArray[indexPath.row]
    cell.configure(...) // call some custom cell configuration function
    return cell
}

This function requires you to return a UITableViewCell which will be inserted at a particular location in the collectionView.

One parameter in this function that is important to take note of is indexPath, which is of type IndexPath. The indexPath has two important properties that you may sometimes need: 1. row and 2. section Thus, if you wanted to see what row or section the cell you’re returning is going to be used for, you can just call indexPath.row or indexPath.section.

Once again, let's go through the code line by line: 2. Dequeue the appropriate cell using the associated reuse identifier that you used when you registered your custom cell to the tableView . When setting it, make sure you cast the dequeued cell as your custom cell class! 3 & 4. It's best practice to have some configure(...) function as a part of your custom cell class that takes in some data or model object to configure the views inside your cell. Let's say you're using some dataModelArray as the data source for your table view. You can access the dataModel object that this row represents using dataModelArray[indexPath.row]. 5. Return the configured cell!

The other method you must implement is called numberOfItemsInSection:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // Each model object in the array is used to configure one cell
    return dataModelArray.count
}

This function requires you to return an integer representing the number of rows that the section section should have. Usually, the number of cells that you want to display in your table view is dependent on the number of model objects that you have. For example, if you wanted to use a table view to display a list of restaurants, you would probably have an array of Restaurant objects. Thus, the number of cells you need in your table view is just the number of restaurants, which is equivalent to restaurantsArray.count. However, it is also possible that you just want to create a static number of cells, in which case you can just return that constant!

A common method that's not required, but you may want to implement is numberOfSections:

func numberOfSections(in tableView: UITableView) -> Int {
    // Return number of sections that you want
    return 2
}

As the name implies, this allows you to control the number of sections that your table view has. If you do no implement this method, your table view will by default have 1 section.

Example: Facebook Messenger

Now that we’ve covered the basics of setting up and creating a UITableView, let's take a look at a real-life example of table views in action and see how we would build it to make sure we fully understand the concepts. The example application we’re going to be looking at is Messenger, used by over 1.3 billion people around the world!

Breaking It Down

Breaking down this table view, we see that each cell (each conversation) has a couple pieces of information:

  1. A profile image

  2. A user’s name/A group name

  3. The latest message sent/received

  4. The timestamp of that latest message.

Let's assume that all of this information is embodied in some User model that we've created. Thus, we would probably have an array of Users in a usersArray . It also seems that this table view is just comprised of one section (since there are no major breaks between groups of cells).

We'll also assume that the height of each cell is 75 pixels and that upon selecting a cell, we want to push another view controller which displays all the conversation history that the logged in user has had with the user/group of the cell that was clicked on. Putting this all together, we have:

1. numberOfRowsInSection

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return usersArray.count
}

Because we want the number of cells to just be the number of users that we have messaged, in numberOfRowsInSection, we can just return usersArray.count.

2. heightForRowAt

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 75
}

Here, we arbitrarily want the height of each cell to be 75 pixels but you can choose this to be some other value if you would like.

3. cellForRowAt

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "messengerCell", for: indexPath) as! MessengerCell
    let user = usersArray[indexPath.row]
    cell.configure(user: user)
    return cell
}

Here, we dequeue a cell and cast it as a MessengerCell (which we'll assume is custom cell class that we've already created!).

Next, we get the appropriate User model for this cell out of the usersArray, use it to configure the cell, and then finally return the cell.

Note: We have to cast the cell as a MessengerCell in order to call configure on cell, since a generic UITableViewCell does not have a configure function.

4. didSelectRowAt

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let user = usersArray[indexPath.row]
    let conversationVC = ConversationViewController(user: user)
    self.navigationController?.pushViewController(conversationVC, animated: true)
}

Here, we have decided to make it so that when a MessengerCell is tapped, we push a ConversationViewController (assuming we've already written this view controller class!) after grabbing the User model associated with this cell.

Reference

You can find a complete list of all the UITableViewDelegate & UITableViewDataSource methods in the Apple documentation, linked below.

PDF Download

Last updated