Up until this point, the ViewController
has conformed to UITableViewDelegate
but you haven't actually implemented any delegate methods yet. Whenever certain interactions occur in UITableView
, such as tapping a cell or swiping on a cell, UITableView
will attempt to notify its delegate about the action that has occurred. There are no required methods in the UITableViewDelegate
protocol, which is why you could conform to it and act as a delegate without writing any implementation code. However, just implementing a list and doing nothing with it is kind of boring so let's add some features that will make UITableView
a little bit more interesting. If you look at the documentation for UITableViewDelegate
you'll see that there's a large collection of methods that we can implement in our app.
There are methods for configuring the height or indentation level. Methods that will notify the delegate when a UITableView
is about to display a cell, or stop displaying it. You can hook into reordering, adding, and deleting cells. You can handle cell selection, highlighting, and more. All of the interactions that are supported by the UITableView
are part of its UITableViewDelegate
protocol. The first thing you will implement is a row selection. Most apps that implement a UITableView
will undertake some kind of an action when a user taps on a row. Once you've implemented cell selection, you will also implement cell reordering and cell removal.
To respond into cell selection, you will have to implement the tableView(_:didSelectRowAt:)
method. Because you've already set the ViewController
instance as the UITableView
delegate, implementing this method is all you have to do for this to work.
The UITableView
will automatically call all of the UITableViewDelegate
methods that its delegate has implemented. When a user taps a cell, an alert will be displayed for now. In
Chapter 3,
Creating a Contact Detail Page, you will learn how to do something more meaningful like displaying a detail page. The following code should be added to ViewController.swift
as follows:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let contact = contacts[indexPath.row] let alertController = UIAlertController(title: "Contact tapped", message: "You tapped (contact.givenName)", preferredStyle: .alert) let dismissAction = UIAlertAction(title: "Ok", style: .default, handler: {action in tableView.deselectRow(at: indexPath, animated: true) }) alertController.addAction(dismissAction) present(alertController, animated: true, completion: nil) }
The preceding code implements the tableView(_:didSelectRowAt:)
delegate method. This method receives two arguments, the UITableView
that called this method and the IndexPath
where the selection occurred. Most of the UITableViewDelegate
methods will receive an IndexPath
argument, and often you will want to use this argument in your implementation. In this example, the indexPath
argument is used to retrieve the contact details that belong to the tapped cell.
Next, a UIAlertController
is instantiated. We will give it a title and a customized message that will refer back to the tapped contact. It also has a preferred style of .alert so it will display as an alert modal. The UIAlertController
also needs to have an action associated with it for the alert to work. A single dismiss action should be enough for now.
It's given a title of Ok
and the style is set to .default so the text in this action will be styled as the default action. Finally, we pass in a handler closure. This closure will be called if the user selects this action by tapping on it. To make sure the UITableView
deselects the currently tapped row so it doesn't remain highlighted all the time, tableView.deselectRow(at:animated:)
is called inside of this handler to reset the selection.
Then, the action is added to alertController
, and it is presented on the current UIViewController
. If you hit build and run now, you can tap on a cell and you will see the alert modal pop up. Tapping on Ok will dismiss the alert and deselect the selected row.
Even though setting this up wasn't very complex, it's really powerful. The delegation pattern makes it really easy to hook in to UITableView's
actions without a lot of boilerplate code. You could even write a dedicated class or struct that conforms to UITableViewDelegate
and use that as the delegate for your UITableView
. This means you could split up ViewController
and UITableViewDelegate
, and that would allow you to reuse the UITableViewDelegate
implementations across other classes. We won't do that in this chapter, but if you'd like you can try to do it. It will truly help you to gain a deeper understanding of delegation and why it's such a powerful technique.
Now that we have covered selecting rows we'll move on to something that's slightly more complex: deleting cells. Deleting data from UITableView
is a feature that many apps implement. Your contacts app will implement this too. We won't actually be deleting contacts from the user's address book even though this would be possible.
In this example, you'll be deleting contacts from the array of contacts that we use to populate the UITableView
. In order to support this deletion, you need to implement another UITableViewDelegate
method. This time you'll have to implement tableView(_:editingStyleForRowAt:)
. This delegate method will be called whenever the user swipes from right to left over a UITableViewCell
. Note that the UITableView
is clever enough to notice that you currently don't have this method implemented, so the Delete button will not be shown when you swipe.
After adding the following code to the ViewController.swift
file, the Delete button will appear if the user performs a swipe gesture:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { contacts.remove(at: indexPath.row) } }
If you build and run the app now and you swipe over a cell, you will be able to tap the delete
action. But nothing happens to the row if we do this, why is that? The contact is removed from the internal array, but this isn't reflected on UITableView
yet. Let's implement the actual cell removal now. Replace the body of tableView(_:editingStyleForRowAt:)
with the following contents:
if editingStyle == .delete { contacts.remove(at: indexPath.row) tableView.beginUpdates() tableView.deleteRows(at: [indexPath], with: .fade) tableView.endUpdates() }
This snippet removes the contact from the contacts array as it did before. After that, beginUpdates
is called. This tells UITableView
that we are about to update it by either inserting, removing, or selecting rows. In this case, you don't strictly need to do this because you only have a single deletion to perform. If you're doing a more complex update, such as removing multiple rows or simultaneously adding and removing rows in your UITableView
, you are required to call beginUpdates
in order to ensure that the UITableView
doesn't reload its data in the middle of the update sequence you're trying to perform.
Once you have made all of the required updates to your rows, in this case deleting a single row, you should call endUpdates
. Calling this method tells UITableView
that you have performed all of the updates that you intended to perform and that it can begin animating them. This sequence of beginUpdates
, performing updates, and endUpdates
is also used when you're inserting or removing sections.
Now that we have properly implemented the removal of cells, let's build and run to test the app. If you swipe over a cell now and press the delete button, the cell will fade out and gets removed from the view. Great! Next up, implementing row reordering.
In some applications it makes sense for users to reorder rows in UITableView
, for example, if you are building a list of a user's favorite contacts or a list that simply doesn't have any natural order where it makes sense for users to reorder it to their own taste. In order to implement reordering in UITableView,
we will need to do a couple of things.
First of all, you'll need a way to enter editing mode for UITableView
. You'll do this by wrapping your ViewController
in UINavigationController
. This will provide the app with a nice bar at the top of the screen, which would be a perfect place to add an Edit/Done button.
UIViewController
actually has a very nice convenience method to do this, so we'll make use of that. When you tap the edit/done button, the setEditing(_:animated:)
method is called on ViewController
. We'll override this method so the code can call UITableView's setEditing(_:animated:)
method to make it enter into edit mode. Finally, you need to make sure that the cells are able to be reordered and you'll then implement the tableView(_:moveRowAt:to:)
delegate method to update the contacts array.
So, let's wrap out ViewController
in a UINavigationController
first. Open the Main.storyboard
file and select the ViewController
. Next, in the top menu, click on Editor | Embed In | Navigation Controller. This will make all the required changes to embed the ViewController
inside the UINavigationController
. Now, to add the edit/done button to the navigation bar, open up ViewController.swift,
and add the following line of code to the end of the viewDidLoad
method:
navigationItem.rightBarButtonItem = editButtonItem
This line will add a UIBarButtonItem
that automatically toggles itself and calls setEditing(Bool, animated: Bool)
on the ViewController
. This button is added to the navigationItem.rightBarButton
method of ViewController
so it appears on the right-hand side of the navigation bar.
If you build and run your app now, you should see a top bar and that the top bar contains a button that says Edit. Clicking on this button will toggle the text to say done
. Now, we need to override the setEditing(Bool, animated: Bool)
method. Editing is enabled on UITableView
when the Edit button is clicked. The implementation of this method is added to ViewController.swift
, as follows:
override func setEditing(_ editing: Bool, animated: Bool) { super.setEditing(editing, animated: animated) tableView.setEditing(editing, animated: animated) }
All that happens in this method is the calling of the super class' implementation of the method, and then we call setEditing(_:animated:)
on UITableView
. Doing this will update the state of the UITableView
in a way where it will allow the user to make changes. Try to run your app now and you'll see that tapping the edit button will make a bunch of red circles appear on the left side of your cells.
Tapping the done button will make these circles disappear. However, reordering isn't enabled yet because you still need to make the cells themselves show a reorder control and you need to implement the delegate method that handles row reordering.
First, open up Main.storyboard
again and select your UITableViewCell
. In the Attributes Inspector on the right side, search for the Shows Re-order Controls checkbox. Checking this will make the cell display a special control when UITableView
enters the editing mode. The last step is to update the array of contacts whenever the user has dragged a row from one spot to another. Doing this is very similar to the deletion of cells except you don't have to update UITableView
because the reordering was already performed internally.
You can try this out by building and running your app right now; you will be able to reorder rows but the data will be out of sync with the UI. Add the following code to ViewController.swift
to fix this:
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { let contact = contacts.remove(at: sourceIndexPath.row) contacts.insert(contact, at: destinationIndexPath.row) }
This code implements the delegate method that gets called whenever a UITableView
reorders a row. It contains the source IndexPath
and the destination IndexPath
. The implementation first removes the reordered contact from the array. The removed method will return the removed contact. After removing the contact, we re-insert it at its new index, which is stored in the destinationIndexPath
.
That's all you need to implement, you can now safely reorder your cells and handle the reordering by implementing the appropriate UITableViewDelegate
method. For more delegate methods, you should have a look at Apple's documentation. As mentioned earlier, there are many delegate methods that can be implemented and it's a really powerful way to allow other objects to handle certain tasks on behalf of, in this case, UITableView
.