Learn HTML & CSS Learn HTML & CSS
Only this week 80 % discount on HTML & CSS and JavaScript e-learning courses!

Lesson 5 - Introduction to the important TableView component

In the previous lesson, Simple iOS calculator in Swift, we programmed a simple iOS calculator in Swift. We used StackView to place components on the form under or next to each other. In today's Swift tutorial, we're going to look at TableView. It's the base of many app and we probably can't avoid it. When we open a news app, calls, notes, and many others, the first thing we'll see is TableView.

TableView is an ideal way to present more data to the user, or a data collection if you prefer this term. It can be a list of tasks, contacts, music albums, etc. In these cases, TableView is the obvious choice. It allows us to display practically unlimited number of elements or objects simply under each other and to handle scrolling through them or selecting individual items. Removing is also a peace of cake.

Creating a project

Enough with theory, let's have a look at a simple way to start with TableView. Prepare either a new Xcode iOS app project (Single View App) or use the one from previous lessons.

Now, look for TableView (not Table View Controller, we'll get to that later) in the object library and drop it into your controller located in Main.storyboard. Ideally, set constraints as well which could be, in this case, 0 from all four sides.

Table view in Xcode for iOS in Swift

Our TableView is ready, but how do we display data in it? For that, we need to visit the code, specifically ViewController.swift. First, we have to specify that this controller represents a data source and a delegate for the TableView.

Let's implement these two protocols in our controller:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

But that's not enough. Now we have to set the controller as the DataSource and Delegate of our TableView. We need this to be able to choose the data source and also to react to events, such as selecting a Cell. We can even choose from different approaches.

Connecting TableView to the controller using the mouse

The first approach is to set the DataSource and Delegate directly in the UI designer. Just select the TableView and drag it while holding Ctrl or right-click the ViewController and select dataSource. The same goes for the delegate. Or you could right-click the TableView in the component list and drop the dataSource and delegate from there.

Setting dataSource and delegate to TableView

Now we basically told the TableView that our controller is going to react to the TableView events and provide data at the same time. This way, the TableView can notify us if the user selected an item or performed other actions.

Connecting TableView to the controller in the code

The second approach is to set DataSource and Delegate in the viewDidLoad() method in our code. I personally prefer this approach, I can see the connection and I always know that I haven't forgot it. If there's some error related to TableView, the first thing you should do is to check you connected it properly.

First, you have to create an Outlet for your TableView, which we learned last time. Then, you just have to set your class (i.e. the controller) to dataSource and delegate.

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.dataSource = self
    tableView.delegate = self
}

The necessary preparations are done, let's display something in the TableView.

Protocols implementation

You can't build the app yet, because we've specified some protocols our class is going to implement, but we haven't added any code yet. Swift is expecting that our class is able to provide data for the TableView. For starters, let's just add these two methods:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

}

The first method returns how many rows we have. Let's keep it simple and have only one section for now. The second method is responsible for returning the cell according the a given row (cell) index.

Because both functions starts the same, in the first case, start typing numberOfRowsIn... and Xcode will automatically complete the right method. This is exactly why there are two different parameter names. The first is used to identify the method from the "outside" and the second is used in the method body. Let Xcode autocomplete the second method the same way, by typing cellForRow....

Preparing data

Let's create a simple array which will represent to-do list items. The best practice is to declare our variables and constants right below the class name.

var todos = ["Buy coffee", "Take out the trash", "Netflix and chill"]

List anything you want. The purpose is to have any array with some values for our TableView to display. Now let's modify our two methods:

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

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = UITableViewCell()
    cell.textLabel?.text = todos[indexPath.row]
    return cell
}

Easy stuff. The first method returns the number of items, and the second creates a cell and sets it the text depending on its index. The texts are extracted from our array. You can try to run the app, you'll see all the items listed under each other:

TODO list in Xcode and Swift

TableView under the hood

Remember that this is not what the method with cellForRowAt should look like. I just wanted to show you a functional TableView as quickly as possible. Now, let's have a look at how TableView works internally.

Scrolling through the items is very smooth even with hundreds of cells. It's because TableView is a smart component and keeps only the cells that need to be displayed. We can simply say that it "recycles" the cells. An important method is needed for this:

let recycledCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

There are actually two of these methods. The second one doesn't have the second parameter and you shouldn't use it at all. It's a deprecated alternative, that is there only for backwards compatibility.

Maybe you noticed the withIdentifier parameter which we have to set to make everything work. But before that, we have to prepare our TableView. Open the designer and after selecting the component, set the Prototype Cells attribute in the Attribute Inspector to 1.

Setting up the Prototype Cells to TableView in Xcode

Then, we have to select this prototype cell which will show up in the preview. You can click it directly in the preview or in the component list of this controller on the left.

Selecting the prototype cell in TableView

Finally, we just set the Identifier in the Attribute Inspector to "cell", which we already use in the code above. Of course, you can choose anything else.

TableView Attribute inspector v Xcode

Now run the app, you should see the to-do list.

Why all this work? We've just used the TableView correctly. The component is now ready to display hundreds of rows. Because only the rows being displayed actually exist, a large amount of data won't make the app slower. This isn't a concern in our case, but at least we showed the correct solution.

There's also a second benefit. We're on the right track to create a more complex cell, or a TableView row. It's what the Prototype Cell allows us. We'd simply drop the needed components and create a special class. We'll discuss that later in this course.

Selecting and deleting items

Now let's have a look at how to select and delete individual items in TableView.

Selecting items

We are provided with a method for the situation when the user has selected an item:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {   }

Again, we can just start typing didSelect... and Xcode will autocomplete the method. For now, we won't do anything fancy when selecting a row, let's just print the selected text (into the Output window in Xcode), the selected to-do in our case.

We'll add calling the print() function to the method. We'll know which row has been selected though the row property of the indexPath parameter. We'll use it as the index for our todos[] array:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print(todos[indexPath.row])
}

If you select a row now, the selected to-do is printed to the console. Finally, we'll finish the introduction to TableView by deleting its items.

Deleting items

We are, again, provided with a method for deleting items:

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {  }

By adding this method we basically say we want to modify the TableView. If you swipe left now, a delete button should appear next to individual rows. But it won't work just yet.

Using the editingStyle parameter, we'll determine whether it's a deleting action. If so, we'll delete this item. First, we'll remove it from our todos[] source array, then from the TableView where we can choose an animation as well. The code would look like this:

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        todos.remove(at: indexPath.row)
        tableView.deleteRows(at: [indexPath], with: .fade)
    }
}

You can choose whatever animation you want. The .automatic option is often used to make things easier and consistent.

Static TableView

Finally, let's have a look at how to create a TableView if we know exactly what cells, or sections, we're going to have. If can be, for example, a menu or any other situation that doesn't require reading from any collection. This approach is useful when we want to have a more complex settings in our app. iOS uses TableView for the device settings on every step.

Drop Table View Controller into the project, it's necessary for a static TableView. The only thing we have to do now is to select the TableView and set Content to Static Cells in the Attributes Inspector. Then you can set the number of sections. Every section can have its own title and footer. Select the sections and set everything needed, including the individual rows.

Setting the sections of the TableView in Xcode

The cells don't have to be single text lines only. You can set Style of individual cells and have e.g. a label and a sublabel, add an image and so on. The Accessory property is often set as well (again, in Attributes Inspector). This property can, for example, display an arrow on the left side of the cell, informing the user that the option leads to another screen, and so on.

Sekce v statickém TableView v Xcode

We also need a new class for our new controller. We'll add a new file, but choose Cocoa Touch Class instead of a Swift file. A dialog pops up. In it, we'll set the new class to be a subclass of UITableViewController and name it e.g. SettingsTableViewController. Now, we'll just select our new TableViewController in Main.storyboard and set its Class to SettingsTableViewController in Identity Inspector.

We'll finish our static TableView by reacting to selecting a cell. We don't have to deal with DataSource or Delegate in the SettingsTableViewController, because all that is handled by the UITableViewController class. So we only have to implement the didSelectRowAt method. We'll mark the method by override so we can provide our own implementation:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print("Selected row \(indexPath.row) in section \(indexPath.section)")
}

Again, let's just print something to test it. In a real-life app, a switch would be quite useful (one of few places where it's actually useful), especially if you had a lot of sections and rows. In this method, switch could only handle sections and the individual sections would have its own methods, such as handleFirstSection(row: Int) and so on. It could look like this:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print("Selected row \(indexPath.row) in section \(indexPath.section)")

    switch indexPath.section {
        case 0:
            handleFirstSection(rowIndex: indexPath.row)
        case 1:
            handleSecondSection(rowIndex: indexPath.row)
        default:
            break
    }
}

func handleFirstSection(rowIndex: Int) {
    switch rowIndex {
        case 0:
            // Show account detail
            break
        case 1:
            // Navigate to settings
            break
        default:
            break
    }
}

func handleSecondSection(rowIndex: Int) {

}

Now we finished the introduction to the TableView component. We'll use this new component to create an actual TODO app, including a database. In the next lesson, Don't reinvent the wheel, use CocoaPods, we'll learn how to use CocoaPods that allows us to use a package system and to install libraries easily.


 

 

Activities (2)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!