The introductory section of this chapter mentioned that you would use Contacts.framework to fetch a user's contacts list and display it in a UITableView.
Before we can display such a list, we must be sure we have access to the user's address book. Apple is very restrictive about the privacy of their users, if your app needs access to a user's contacts, camera, location, and more, you need to specify this in your application's Info.plist file. If you fail to specify the correct keys for the data your application uses, it will crash without warning. So, before attempting to load your user's contacts, you should take care of adding the correct key to Info.plist.
To add the key to access a user's contacts, open Info.plist from the list of files in the Project Navigator on the left and hover over Information Property List at the top of the file. A plus icon should appear, which will add an empty key with a search box when you click it. If you start typing Privacy – contacts, Xcode will filter options until there is just one option left. This option is the correct key for contact access. In the value column for this new key, fill in a short description about what you are going to use this access for. In the HelloContacts app, something like read contacts to display them in a list should be sufficient.
Whenever you need access to photos, Bluetooth, camera, microphone, and more, make sure you check whether your app needs to specify this in its Info.plist. If you fail to specify a key that's required, your app crashes and will not make it past Apple's review process.
Now that you have configured your app to specify that it wants to access contact data, let's get down to writing some code. Before reading the contacts, you'll need to make sure the user has given permission to access contacts. Your code will need to check the required permissions first. Once the current permissions have been determined, the code should either fetch contacts or it should ask the user for permission to access the contacts. Add the following code to ViewController.swift. After doing so, we'll go over what this code does and how it works:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let store = CNContactStore()
let authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
if authorizationStatus == .notDetermined {
store.requestAccess(for: .contacts, completionHandler: {[weak self] authorized, error in
if authorized {
self?.retrieveContacts(fromStore: store)
}
})
} else if authorizationStatus == .authorized {
retrieveContacts(fromStore: store)
}
}
func retrieveContacts(fromStore store: CNContactStore) {
let containerId = store.defaultContainerIdentifier()
let predicate = CNContact.predicateForContactsInContainer(withIdentifier:
containerId)
let keysToFetch =
[CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactImageDataKey as CNKeyDescriptor,
CNContactImageDataAvailableKey as CNKeyDescriptor]
let contacts = try! store.unifiedContacts(matching: predicate,
keysToFetch: keysToFetch)
print(contacts)
}
}
In the viewDidLoad method, we create an instance of CNContactStore. This is the object that will access the user's contacts database to fetch the results you're looking for. Before you can access the contacts, you need to make sure that the user has given your app permission to do so. First, check whether the current authorizationStatus is equal to .notDetermined. This means that we haven't asked permission yet and it's a great time to do so. When asking for permission, we make use of a completionHandler. This handler is called a closure. It's basically a function without a name that gets called when the user has responded to the permission request. If your app is properly authorized after asking permission, the retrieveContacts method is called from within the callback handler to actually perform the retrieval. If the app already has permission, the code calls retrieveContacts right away.
Completion handlers are found throughout the Foundation and UIKit frameworks. You typically pass them to methods that perform a task that could take a while and is performed parallel to the rest of your application, so the user interface can continue running without waiting for the result. A simplified implementation of a function that receives a callback looks like the following:
func doSomething(completionHandler: Int -> Void) {
// perform some actions
var resultingValue = theResultOfSomeAction()
completionHandler(resultingValue)
}
You'll notice that the part of the code calling completionHandler looks identical to calling any ordinary function or method. The idea of such a completion handler is that we can specify a block of code, a closure, that is supposed to be executed at a later time. For example, after performing a task that is potentially slow.
You'll find plenty of other examples of callback handlers and closures throughout this book as it's a pretty common pattern in iOS and programming in general.
The retrieveContacts method in ViewController.swift is responsible for fetching the contacts and is called with a parameter named store. It's set up like this so we don't have to create a new store instance as we already created one in viewDidLoad. When fetching a list of contacts, you use a predicate. We won't go into too much detail on predicates and how they work yet because that's covered more in depth in Chapter 9, Storing and Querying Data in Core Data. The main purpose of a predicate is to set up a filter for the contacts database.
In addition to a predicate, you also provide a list of keys you want to fetch. These keys represent properties that a contact object can have. Each property represents a piece of data. For instance there are keys that you can specify for retrieving the contact's e-mail address, phone numbers, names, and more. In this example, you only need to fetch a contact's given name, family name, and contact image. To make sure the contact image is available at all, there's a key for that as well.
When everything is configured, a call is made to unifiedContacts(matching: keysToFetch:). This method call can throw an error, and since we're currently not interested in the error, try! is used to tell the compiler that we want to pretend the call can't fail and if it does, the application should crash.
When you're writing real production code, you'll want to wrap this call in do {} catch {} block to make sure that your app doesn't crash if errors occur. The following is a very simple example of this:
do {
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keysToFetch)
print(contacts)
} catch {
// fetching contacts failed
}
If you run your app now, you'll see that you're immediately asked for permission to access contacts. If you allow this, you will see a list of contacts printed in the console. Next, let's display some of this fetched information in the contacts table instead of printing it to the console!