• 20 hours
  • Medium

Free online content available in this course.

Videos available in this course

Certificate of achievement available at the end this course

Got it!

Implement lists using tables

Log in or subscribe for free to enjoy all this course has to offer!

Tables are used to present content in a form of lists using a UIKit element: Table View. Table View is certainly one of the most popular components that can be found in nearly every app!

Table View is a sophisticated UI component that incorporates various sub-elements, the main ones being:

  • Table header

  • Table footer

  • Sections

  • Cells

Table view acts like a container for all the components within it and also provides a close collaboration with a view controller. This collaboration process is implemented using protocols and delegates

We briefly touched on these definitions in previous courses. Now, let's make sure we comprehend all the details.

Nuts and bolts of Delegates & Protocols

These two definitions are extremely important in Swift (and many other programming languages) and extensively utilized with many objects, not just Table Views.

Let's start with delegation.

To speed up our experiments, we'll use a Playground project. Go ahead and create one, and name it "Delegation.playground."

Understanding Delegation

Delegation is a software implementation approach that allows one object to act on behalf of another.

Let’s take a car, for example - it needs to be driven. This action is delegated to humans. To implement this approach in code, we need 3 elements:

  • Car class

  • Human class 

  • Delegation "link"

Delegation example
Delegation example

What's that link

The linking part is done via a delegate property that we must declare in the object that will be delegating (A car, in our example). Grab this initial code:

import UIKit

class Car {
    var color = UIColor.red
    var brand = "Ferrari"
    
    var delegate: AnyObject?
}

class Human {
    var height = 170.0
}

The two classes we declared have all the familiar elements. Let's look closer at the delegate property. For now it looks like an ordinary property of optional AnyObject type:

var delegate: AnyObject?

We'll use this property to assign a Human object, which will be responsible for driving.

Wait, can any human drive a car? 😒 👶

Valid concern! Not every human can drive a car. For that matter, we need to make sure the one we are using as a delegate has the required attributes and skills, like has a driving license and is able to drive.

How can we make sure of that? 😅

This is where protocols come into play.

Discovering Protocols

A protocol is a set of requirements. Those requirements ensure that a delegate object can perform delegated actions and has the required attributes.

An object that wants to be a delegate must satisfy those requirements, or in other words, must conform to a protocol:

Delegation using protocols
Delegation using protocols

In our car example, we can declare a Driver protocol, which will be required to have a driver's license as a property, and to be able to drive a car as a method.

Here's what our requirements may look like:

protocol Driver {
    var driverLicense: String? { get set }
    func drive()
}

As you can see, the declarations are similar to those of a class

Important points to note here:

  • Properties of a protocol must specify whether they are read/write or read-only: 

    var readWriteProperty: String? { get set } // read/write property
    var readOnlyProperty: String? { get } // read-only property
  • Method declarations don't have a body (no curly brackets), as there's no need for any particular implementation.

There are two categories of protocol member declarations:

  • Required

  • Optional

By default, the declared members are required. To declare optional ones, we must use a trick:

@objc protocol ProtocolWithOptionalMembers {
    func requiredMethod()
    @objc optional func optionalMethod()
}

Back to our Driver example. Two things must happen next:

  • A delegating object (Car) must specify that only objects that conform to the Driver protocol are allowed to be delegates. 

  • An object that wants to be a driver must declare that it conforms to the Driver protocol and must implement properties and methods required by the protocol.

Declaring delegate protocol

To declare that a delegate object must conform to a particular protocol, we simply change our type declaration:

var delegate: Driver?

A delegate object may be required to conform to more than one protocol, in which case we simply list all multiple protocols in the same declaration:

var delegate: (Driver, Mechanic)?

And we can also separate them by using different delegate properties:

var driverDelegate: Driver?
var mechanicDelegate: Mechanic?

The naming for protocols we've used so far may be confusing. Driver and Mechanic at first glance can refer to classes (or structures). For that matter, it's common to use the  Delegate  suffix in the protocol name:

protocol DriverDelegate {
}

protocol MechanicDelegate {
}

class Car {
    var driverDelegate: DriverDelegate?
    var mechanicDelegate: MechanicDelegate?
}

The next phase: associating delegate objects with protocols!

Declaring conformation to a protocol

To declare that a class conforms to a protocol, we simply list the protocol name after the class name with :  just like we would to list a parent class we want to inherit:

class className: protocolName {
    
}

And if we used a subclass instead, the full declaration would look like this:

protocol ProtocolName {
    
}

class ClassName {
    
}

class SubClassName: ClassName, ProtocolName {
    
}

So, our Human/Driver conformation will look like this:

class Human: Driver {
    var height = 170.0
    
    var driverLicense: String?
    
    func drive() {
    }
}

Let's review the above:

  • we kept our  height  property - a normal property of a class

  • we declared a required property - driverLicense   of  Driver  protocol 

  • we declared a required method - drive() of Driver protocol.

Object linking
Object linking

Now for the fun part! 🎉

Protocol Inheritance

Just like classes, protocols support inheritance! And the declaration is identical to the one for classes:

class SubProtocolName: ProtocolName {
    
}

So, how can we enhance our drivers?

Using protocol inheritance, we can create a new, very specific, protocol -  FerrariDriver  :

protocol FerrariDriver: Driver {
    var extremeDriverLicense: String? { get set }
}

And then, do some magic! 💫

1. Declare a special class of super humans that inherit all the elements of   the Human  class (a.k.a. subclass  Human   class) and conform to a  FerrariDriver  protocol! 💪

class SuperHuman: Human, FerrariDriver {
    var extremeDriverLicense: String?
}

There're a couple of Important things to point out here:

  • We did not have to repeat declaration of the original Driver protocol members - the  driverLicense  property and  drive()   method - because we have already declared them in the  Human  class declaration that our newSuperHumanclass inherits

  • We DID have to declare a property that is required by the  FerrariDriver  protocol: extremeDriverLicense.

  • And, of course, our subclass inherits the  height  property that we defined in the Human class. 

2. Another fun variation we can try: let's declare a completely different class bypassing the   Human  class inheritance altogether with superSpecies:

class SuperSpecies: FerrariDriver {
    var height = 170.0
    
    var driverLicense: String?
    var extremeDriverLicense: String?
    
    func drive() {
    }
}

This declaration is very different from the one in the previous variation:

  • We still needed to declare the new property  extremeDriverLicense  as required by  FerrariDriver  protocol.

  • We also had to declare properties and methods required by  Driver  protocol -  driverLicense   and  drive()because these declarations are no longer inherited.

  • And, we declared its own  height  property, again, because we no longer inherit this property. 

Let's put our experimental code snippet together:

import UIKit

protocol Driver {
    var driverLicense: String? { get set }
    func drive()
}

class Car {
    var color = UIColor.red
    var brand = "Ferrari"
    
    var delegate: Driver?
}

class Human: Driver {
    var height = 170.0
    
    var driverLicense: String?
    
    func drive() {
    }
}

Now, back to our Table View! Let’s see what we need to do to make it "collaborate" with a view controller.

Introducing UITableView

Table Views in UIKit are represented by  UITableView  class. Let's switch to our storyboard, drag one of those to our app screen, and size it to the size of the screen:

Add UITableView to the main screen
Add UITableView to the main screen

Let's connect it to the code by creating an outlet:

Create an outlet for table view
Create an outlet for table view

Declaring conformation

To make a TableView object "collaborate" with the view controller we must implement delegation.

First, let's declare that our View Controller conforms to two required protocols:

  • UITableViewDelegate

  • UITableViewDataSource

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    // ...
}

As soon as we add this conformation code, the Xcode starts complaining, stating that our ViewController does NOT conform to declared protocols:

Xcode's not happy:(
Xcode's not happy ☹️

That's because we haven't implemented all the required methods of those protocols yet. Not to worry!

Let's assign our view controller as a delegate to the table view. To keep the code organized, we'll create a  configure()   method and call it in a viewDidLoadmethod: 

func configure() {
    tableView.delegate = self
    tableView.dataSource = self
}
    
override func viewDidLoad() {
    super.viewDidLoad()
    configure()
}

Declaring required methods

Oh no! The Xcode is still complaining... 😱

We've got two delegates here:

  • An object that will act as a data source for the table (a.k.a. a content feeder to the table). It needs to implement all the essential methods that will provide the minimum information necessary for the table to populate the table's content.

  • An object that will act as delegate for table layout and activities as UI element, such as what to do before or after cells display, how to display headers and footers, etc. All methods of this protocol are optional.

Could those two delegates be different objects? 🤔

Those two delegates definitely could be different objects and don't have to be a view controller. However, in the context of MVC architecture it should be some sort of controller. Typically, the same object is used as a delegate for both properties, and it's usually a view controller, as it's meant to manage one of the views:  UITableView .

There are three required methods that a table view data source delegate must implement:

func numberOfSections(in tableView: UITableView) -> Int {
    return 0
}
    
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 0
}
    
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    return UITableViewCell.init()
}

Let's review them:

  • numberOfSections  returns a number of sections in the table (we'll learn about table sections in the next chapter);

  • numberOfRowsInSection  returns number of rows for each section using a section index;

  • cellForRowAt  provides a cell object with actual content for each cell by an indexPath.

Let's briefly go over a few new definitions:

  • Table Section - a group of table rows. A table may have 0 or more sections. Sections are indexed starting from 0 and are of  Int  type.

  • Table Row - a line in a table view that contains ONE cell. Table Views accommodate one cell per row. Rows are indexed starting from 0 and are of  Int  type.

  • Index Path - a composition of section index and row index. This combination is represented by the structure  IndexPath. It allows us to identify the exact position of a cell in a table.

Let's visualize the above elements (you can test our current app implementation and see how it appears in a simulator):

Table View essential components
Table View essential components

We will review these components in detail and implement some optional methods of datasource and table view delegated in the following chapter!

Cheating tools: UITableViewController

Just kidding, no cheating here. Do you remember that  TableView  is an extremely common element in iOS apps? Because of this, there's a convenient controller element for this: UITableViewController.  UITableViewController   inheritsUIViewController and automatically implements  UITableViewDelegate  andUIDataSourceDelegate. It also implements some layout adjustments.

Which one to use? 

UITableViewControlleris good for quick implementation. In more complex projects, however,  UITableView  is more commonly used paired withUIViewController , which implements the necessary delegates.

Let's Recap!

  • Table View is a container-like UI element that consists of sub-elements:

    • Table Footer

    • Table Header

    • Section Headers

    • Section Footers

    • Table Cells

  • In order to declare a delegate object that conforms to a protocol, we need to declare a variable that conforms to a protocol similar to class type:  var delegate: ProtocolName?

  • In order to declare conformation to a protocol, classes need to declare inheritance capabilities:  class ClassName: ProtocolName {}

  • Protocols support inheritance

  • In order to work with table views, a view controller must conform to the following protocols:

    • UITableViewDelegate

    • UITableViewDataSource

  •  UITableViewDataSource requires implementation of the following methods:

    • numberOfSections  - to supply the number of sections in a table

    • tableView/numberOfRowsInSection  - to supply the number of rows for each section

    • tableView/cellForRowAt/indexPath  - to supply a cell for each row

In the next part of the course, you'll learn how to manage table sub-elements.

Example of certificate of achievement
Example of certificate of achievement