Conforming to the UITableViewDataSource and UITableViewDelegate protocols

To set up the table view's delegate and data source, you need to create an @IBOutlet for the table view in ViewController.swift. Add the following line to your ViewController class, just before viewDidLoad():

@IBOutlet var tableView: UITableView!

Now, using the same technique as you used before when connecting outlets for your table view cell, select the table view in Main.storyboard and use the Connections Inspector to connect the outlet to the table view.

To make ViewController both the delegate and the data source for its table view, it will have to conform to both protocols. It is a best practice to create an extension whenever you make an object conform to a protocol. Ideally, you make one extension for each protocol you want to implement. Doing this helps to keep your code clean and maintainable.

Add the following two extensions to ViewController.swift:

extension ViewController: UITableViewDataSource {
  // extension implementation
}
extension ViewController: UITableViewDelegate {
  // extension implementation
}

After doing this, your code contains an error. That's because none of the required methods from UITableViewDataSource have been implemented yet. There are two methods you need to implement to conform to UITableViewDataSource. These methods are tableView(_:numberOfRowsInSection:) and tableView(_:cellForRowAt:).

Let's go ahead and fix the error Xcode is showing by adjusting the code a little bit. This is also a great time to refactor the contact-fetching code a little bit. You will want to access the fetched contacts in multiple places, so the list should be an instance variable on the view controller. Also, if you're adding code to create cells anyway, you might as well make them display the correct information.

Add the following updates to ViewController.swift:

class ViewController: UIViewController {

  var contacts = [CNContact]()

  // viewDidLoad
  // retrieveContacts
}

extension ViewController: UITableViewDataSource {
  // 1
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return contacts.count
  }

  // 2
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // 3
    let cell = tableView.dequeueReusableCell(withIdentifier: "ContactTableViewCell") as! ContactTableViewCell
    let contact = contacts[indexPath.row]

    cell.nameLabel.text = "(contact.givenName) (contact.familyName)"

    // 4
    if contact.imageDataAvailable == true, let imageData = contact.imageData {
      cell.contactImage.image = UIImage(data: imageData)
    }

    return cell
  }
}

The preceding code completes the implementation of UITableViewDataSource. Let's go over the commented sections of code to clarify them a little bit:

  • Since this table view only has a single section, the number of contacts as returned for the number of items in every section. This is OK because we know that there will always be just a single section. When you build an app that shows a table view with multiple sections, you would have to implement the numberOfSections property to inform the table view about the number of sections it needs to render.
  • This method is responsible for creating and configuring one of the ContactTableViewCell cells you created earlier.
  • Earlier in this chapter, you learned that cells are reused, that's why you had to set a reuse-identifier in the storyboard. Here, the reuse-identifier is used to ask for a cell to display a fetched contact in. Reusing cells that have been scrolled off screen is a performance optimization that enables a table view to display vast amounts of items without choppy scrolling or consuming tons of memory. The dequeueReusableCell(withIdentifier:) method has UITableViewCell as its return type. Therefore, you need to cast the result of that method to be the cell you set up in Interface Builder earlier. In this case, that is ContactTableViewCell.
  • The last step safely extracts image data from the contact if it's available. If it is, the image data is used to set up an image for the cell.

This doesn't wrap up the refactoring of fetching contacts just yet. Contacts are being fetched, but the array of contacts you added to ViewController earlier is not set up correctly yet, the fetched contacts are not attached to this array. In its current state, the last couple of lines in retrieveContacts look as follows:

let contacts = try! store.unifiedContacts(matching: predicate, keysToFetch: keysToFetch)
print(contacts)

Change these lines to the following code:

contacts = try! store.unifiedContacts(matching: predicate, keysToFetch: keysToFetch)
DispatchQueue.main.async { [weak self] in
  self?.tableView.reloadData()
}

With this update, the result of fetching contacts is assigned to the variable you created earlier. Also, the table view is instructed to reload its data. Note that this is wrapped in a DispatchQueue.main.async call. Doing this ensures that the UI is updated on the main thread. Since iOS 11, your app will crash if you don't perform UI work on the main thread. If you want to learn more about this, have a look at Chapter 25, Offloading Tasks with Operations and GCD, it covers threading in more depth.

There is one more step before you're done. The table view is not aware of its dataSource and delegate yet. You should update the viewDidLoad() method to assign the table view's dataSource and delegate properties. Add the following lines to the end of viewDidLoad():

tableView.delegate = self
tableView.dataSource = self

Now go ahead and run your app. If you're running your app on the simulator, or you don't have any images assigned to your contacts, you won't see any images. You can add images to contacts in the simulator by dragging images from your Mac onto the simulator and saving them in the photo library. From there, you can add pictures to contacts the same way you would do it on a real device. If you have a lot of contacts on your device, but don't have an image for everybody, you might encounter an issue when scrolling. Sometimes, you might see a picture of a different contact than the one you expect! This is a performance optimization that is biting you. Let's see what's going on and how to fix this bug.

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

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