Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Mastering iOS 12 Programming

You're reading from   Mastering iOS 12 Programming Build professional-grade iOS applications with Swift and Xcode 10

Arrow left icon
Product type Paperback
Published in Oct 2018
Publisher Packt
ISBN-13 9781789133202
Length 750 pages
Edition 3rd Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Donny Wals Donny Wals
Author Profile Icon Donny Wals
Donny Wals
Arrow right icon
View More author details
Toc

Table of Contents (29) Chapters Close

Preface 1. UITableView Touch-up 2. A Better Layout with UICollectionView FREE CHAPTER 3. Creating a Detail Page 4. Immersing Your Users with Animation 5. Understanding the Swift Type System 6. Writing Flexible Code with Protocols and Generics 7. Improving the Application Structure 8. Adding Core Data to Your App 9. Fetching and Displaying Data from the Network 10. Being Proactive with Background Fetch 11. Syncing Data with CloudKit 12. Using Augmented Reality 13. Improving Apps With Location Services 14. Making Smarter Apps with CoreML 15. Tracking Activity Using HealthKit 16. Streamlining Experiences with Siri 17. Using Media in Your App 18. Implementing Rich Notifications 19. Instant Information with a Today Extension 20. Exchanging Data With Drag And Drop 21. Improved Discoverability with Spotlight and Universal Links 22. Extending iMessage 23. Ensuring App Quality with Tests 24. Discovering Bottlenecks with Instruments 25. Offloading Tasks with Operations and GCD 26. Submitting Your App to the App Store 27. Answers 28. Other Books You May Enjoy

UITableViewDelegate and interactions

So far, the ViewController class has implemented the UITableViewDelegate protocol but none of the delegate methods have been implemented yet. Any time certain interactions occur on a table view, such as tapping a cell or swiping on a cell, the table view will attempt to call the corresponding delegate methods to inform the delegate about the action that occurred.

The UITableViewDelegate protocol does not specify any required methods, which is why it has been possible to conform to this protocol without actually doing work. Usually, you will want to implement at least one method from UITableViewDelegate because simply displaying a list without responding to any interactions is quite boring. Let's go ahead and explore some of the methods from UITableViewDelegate to create a better experience for Hello-Contacts. If you take a look at the documentation for UITableViewDelegate, you'll notice that it has a large collection of delegate methods that you can implement in your app.

You can hold the Alt key when clicking on a class, struct, enum, or protocol name to navigate to the documentation for the type you clicked on.

The documentation for UITableViewDelegate lists methods for configuring cell height, content-indentation level, cell-selection, and more. There are also methods that you can implement to get notified when the table view is about to display a cell or is about to stop displaying one. You can handle cell-selection, cell-highlighting, reordering cells, and even deleting them. One of the more common methods to implement is tableView(_:didSelectRowAt:). After you have implemented this method, you'll also implement cell-reordering and -removal.

Responding to cell-selection

Cell-selection refers to a user tapping on a cell. In order to respond to cell-selection, the UITableViewDelegate method called tableView(_:didSelectRowAt:) should be implemented. In Hello-ContactsViewController already has an extension implemented to make it conform to UITableViewDelegate so all you need to do is implement tableView(_:didSelectRowAt:) in the extension.

Since a table view will call methods on its delegate whenever they are implemented, you don't need to tell the table view that you want to respond to cell-selection. This automatically works if the table view has a delegate, and if the delegate implements tableView(_:cellForRowAt:). The implementation you'll add to Hello-Contacts, for now, is a very simple one. When the user taps a cell, the app displays an alert. In Chapter 3, Creating a Detail Page, you will learn how to perform more meaningful actions such as displaying a detail page. Add the following code to the UITableViewDelegate extension in ViewController.swift:

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 tableView(_:cellForRowAt:) method receives two arguments, the first is the table view that called this delegate method. The second argument is the index path at which the selection occurred. The implementation you wrote for this method uses the index path to retrieve the contact that corresponds with the tapped cell so the contact name can be shown in an alert. You could also retrieve the contact's name from the tapped cell. However, this is not considered good practice because your cells and the underlying data should be as loosely-coupled as possible. When the user taps the Ok button in the alert, the table view is told to deselect the selected row. If you don't deselect the selected row, the last tapped cell will always remain highlighted. Note that the alert is displayed by calling present(_:animated:completion:) on the view controller. Any time you want to make a view controller display another view controller, such as an alert controller, you use this method.

Even though this setup is not extremely complex, there is some interesting stuff going on. The delegation pattern is a very powerful one when implemented correctly. Especially in the case of a table view, you can add a lot of functionality simply by implementing the delegate methods that correspond to the desired behavior. Because the table view's delegate could be any object that conforms to UITableViewDelegate, you could split up ViewController and UITableViewDelegate entirely. Doing so would enable you to reuse your delegate implementation across multiple table views. For now, I'll leave it as an exercise for you to do this. Attempting such a refactor will certainly help you to increase your understanding of delegation and its powers.

Try to extract your delegate and/or data source for the table view to a separate class or struct. This will allow 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 you know how to respond to cell-selection, let's have a look at a slightly more advanced topic  cell-deletion. Deleting data from a table view is a feature that many apps implement. If you have ever used the mail app on iOS, you might have noticed that several actions appear when a user swipes either right or left on a table-view cell. These swipe actions are a great feature to implement for Hello-Contacts so users can swipe over contacts and easily delete them. Of course, we won't be actually deleting contacts from a user's address book, but it would be possible to implement this if you truly wanted to.

In this example, you'll learn how to delete contacts from the array of contacts that is used to populate the table view. To implement support for cell-deletion, you need to implement another method from UITableViewDelegate. The method you need to implement is tableView(_:trailingSwipeActionsConfigurationForRowAt:). This delegate method is called when a user swipes over a table view cell and returns the actions should be shown when the cell moves sidewards. A good example of this is found in the mail app on iOS.

Add the following implementation of tableView(_:trailingSwipeActionsConfigurationForRowAt:) to the UITableViewDelegate extension for ViewController:

func tableView(_ tableView: UITableView,
               trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

  // 1
  let deleteHandler: UIContextualActionHandler = { [weak self] action, view, callback in
    self?.contacts.remove(at: indexPath.row)`
    callback(true)
  }

  // 2
  let deleteAction = UIContextualAction(style: .destructive,
                                        title: "Delete", handler: deleteHandler)

  // 3
  return UISwipeActionsConfiguration(actions: [deleteAction])
}

If you run the app now and swipe from right to left over a table view cell, a delete button will appear from underneath the cell. In the code snippet, the first step is to set up a delete-handler that takes care of the actual deletion of the contact. This handler is a closure that is passed to the UIContextualAction instance that is created in step two. You have seen closures passed directly to method calls already, for instance as completion-handlers. However, you can also store a closure in a variable. This allows you to reuse your closure in several places and can make your code more readable. The third and last step is to create an instance on UISwipeActionsConfiguration and pass it the actions that you want to display. Since you can pass an array of actions, it is possible to show multiple actions when the user swipes over a cell. In this case, only a single action is added  the delete action.

Currently, tapping the delete button doesn't do much. While the contact is removed from the underlying data source, the table view itself doesn't update. Table views don't automatically stay in sync with their data sources. Add the following deleteHandler implementation to make sure the table view updates when the user taps the delete button:

let deleteHandler: UIContextualActionHandler = { [weak self] action, view, callback in
  self?.contacts.remove(at: indexPath.row)
  self?.tableView.beginUpdates()
  self?.tableView.deleteRows(at: [indexPath], with: .fade)
  self?.tableView.endUpdates()
  callback(true)
}

This new version of deleteHandler ensures that the table view updates itself by removing the row that the user has decided to remove. Note that the contacts array is updated before updating the table view. When you update the table view like this, it will verify that it is in sync with the data source, which is the contacts array in this case. If the data source does not contain the expected amount of sections or rows, your app will crash. So whenever you update a table view, make sure to update the data source first. Also, note the calls to beginUpdates and endUpdates. These methods make sure that the table view doesn't reload in the middle of being manipulated. This is especially useful if you're performing a lot of complex updates, such as moving cells, inserting new ones, and removing old ones all at the same time.

With cell-deletion out of the way, let's have a look at reordering cells.

Allowing the user to reorder cells

In some applications, it makes sense for users to be able to reorder cells that are shown in a table view, such as in a to-do list application or a grocery list. Implementing cell-reordering takes a couple of steps. First, a table view needs to be put in editing mode. When a table view is in editing mode, the user can begin sorting cells visually. Typically, a button in the top right or left corner of the screen is used to enter and exit the editing mode. The easiest way to make space for a button is by wrapping your ViewController in a UINavigationController. Doing this makes a navigation bar appear at the top of the screen that has space for a title, back button, and also for custom buttons such as the Edit/Done button we need to make the table view enter and exit the editing mode.

Placing the table view in editing mode is actually really simple if you know how. Every UIViewController instance has a setEditing(_:animated:) method. If you override this method, you can use it as an entry point to call setEditing(_:animated:) on the table view so it enters edit mode. Once this is implemented, you need to implement tableView(_:moveRowAt:to:) from UITableViewDelegate to commit the reordered cells to your data source by updating the contacts array.

First, open Main.storyboard so you can wrap the view controller in a navigation controller. When you have selected the view controller in your storyboard, click Editor | Embed In | Navigation Controller in the menu bar at the top of the screen. This will automatically embed the view controller in a navigation controller and configure everything as needed. To add the Edit/Done button, open ViewController.swift and add the following code to viewDidLoad:

navigationItem.rightBarButtonItem = editButtonItem

This line adds a UIBarButtonItem that automatically toggles itself and calls setEditing(_:animated:) on the view controller. Since it's set as rightBarButtonItem, it will appear on the right side of the navigation bar. If you go ahead and build the app now, you'll see that you have a button that toggles between a label that says Edit and Done. To put the table view in edit mode, you must override the setEditing(_:animated:) method in ViewController.swift, as follows:

override func setEditing(_ editing: Bool, animated: Bool) {
  super.setEditing(editing, animated: animated)

  tableView.setEditing(editing, animated: animated)
}

What this method does should be self-explanatory. Go ahead and run the app now. If you tap the Edit button, every cell suddenly displays a red circle  while this is interesting, it's not quite what's needed. Cells don't show reorder controls when in edit mode by default. Open Main.storyboard again and select your prototype cell. In the Attributes inspector, you'll find a checkbox named Shows Re-order controls. You want to make sure this checkbox is checked so you can reorder cells.

The final step to implementing this feature is adding tableView(_:moveRowAt:to:) in the UITableViewDelegate extension in ViewController.swift. This method will make sure that the contacts array is updated in the same way the cells are, ensuring that the table view and data source remain nicely in sync. Add the following code to the UITableViewDelegate extension in ViewController.swift:

func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
  let contact = contacts.remove(at: sourceIndexPath.row)
  contacts.insert(contact, at: destinationIndexPath.row)
}

Even though it's only two lines, this code updates the data source by moving a contact from its old position in the array to its new position. You now have everything in place to correctly reorder cells in a table view. Go ahead and try it out!

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at ₹800/month. Cancel anytime