UITableViewDelegate and interactions

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.

Note

You can hold the alt key when clicking on a class, struct, enum, or protocol name to make an information dialog pop up. From this dialog pop up, you can navigate to the documentation for the definition you clicked.

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.

Responding to cell selection

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.

Note

Try to extract your delegate and/or data source for UITableView out to a separate class or struct. This will enable you to reuse your code, and you will gain a deeper understanding of what delegation is and how it works.

Implementing cell deletion

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.

Allowing the user to reorder cells

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.253.198