As every iOS release, the version 9, which is officially here for just a few weeks, presents new features and improvements to existing technologies for both users and developers. As we’ve all witnessed, there’s a number of new stuff that has been first-introduced in this version, but there are changes and updates to existing frameworks and libraries as well. Additionally, and that’s always surprising, there are cases where old APIs are left aside and become deprecated, making room for new ones that have been implemented from ground up to fill in the gap instead. The greatest example in iOS 9 is the all brand new Contacts framework, which is here to replace the old AddressBook framework in a fashion modern, simple and a lot more straightforward.
Every developer that has dealt with the AddressBook API in the past can definitely say that it wasn’t the most easy part of the iOS SDK to work with. In general, the AddressBook was difficult to understand and manage, and that fact was even more intense in new developers. All that belongs to history, as the new Contacts framework is way simpler to understand and use; contacts can be fetched, created or updated in no time at all, the contacts-related development time can be dramatically decreased, changes and modification can be done really fast.
In the next few paragraphs we’ll highlight the most important aspects of the Contacts framework. I won’t go into much details, as you can find them all in the official Apple documentation, and the WWDC 2015 session 223 video.
So, first of all, I’ll begin by something crucial, and that is the user privacy. Users are always being asked if they grant access to their contacts data through an application. If they do so, then the app can freely interact with the user’s contacts database. If, on the other hand, users prohibit access to the contacts data, then this decision must be respected by the app and not interact with it at all. In a while we’ll talk more specifically about it, and we’ll see how all possible situations are handled programmatically. Furthermore, keep in mind that the user is always eligible to change the authorization state of the app through the device Settings, so you should always check if your app has the required permissions to access the contacts data or not right before you perform any related task.
The main source of contacts data is always the database existing in a device. However, the Contacts framework does not lookup just there when an app asks for contact data. In fact, it searches for contacts in other sources too, like your iCloud account (if you’re connected to it of course), and returns back to the app unified contacts that originate from various sources. This is quite useful, as you don’t have to initiate separate searches for contacts residing in databases other than the device’s. You have them all at once, and you manage them as you like.
The Contacts framework has a number of classes that serve specific purposes. All of them have their importance, but the one that is used the most, is called CNContactStore. This one represents the contacts database programmatically, and provides various methods for performing tasks like fetching, saving or updating records, authorization check and authorization request, and many more. A single contact is represented by the CNContact class, but keep in mind that the properties of that class are immutable. If you want to create a new contact or update an existing one, you must use the CNMutableContact one. Note that when dealing with the Contacts framework, and especially when fetching contacts, you should always perform those tasks in background threads. If a contact fetching task takes too much time and is working in the main thread, then your app may become unresponsive, and eventually end up to a bad user experience.
Rarely all of the contact properties are really needed when importing contacts into an app. Fetching all data for all contacts existing in all sources that the Contacts framework searches into, can be proved a resource-eating process, so you should avoid doing that unless you’re sure that you’re going to use all of the data pieces, up to the last one. Thankfully the Contacts framework provides ways for fetching partial results, meaning just a subset of properties regarding a contact. For example, you could ask only for the first and last name, the home email address, and the home phone number, and save that way a lot of resources by leaving aside all that data you don’t really need.
Besides all the programmatic access that the Contacts framework provides, it also supports some default UI that can be incorporated in applications and have that way direct and visual access to contacts. The provided UI is pretty much same to the Contacts app, so that means that there’s a contact picker view controller along with the details card that can be used to pick contacts and properties (it can be customized up to a level), and a contacts view controller to display the contact details and perform certain actions (for example, to make a call).
All of the above are aspects that we’ll see in details in the following parts of this tutorial. Once again, visit the official documentation for additional information on what I presented or what I’ll present from now on. Let’s see now what the demo application is going to be, and then let’s get started dealing with the Contacts framework classes. You’ll find out that it’s easy and funny messing with this new framework.
A Quick Look at the Demo App
Using the demo application of this tutorial I’ll try to present to you as many aspects as I can of this new framework. Actually, through the next parts I’ll show you how to:
- Check if the app is authorized to access contacts and how to request for access.
- Fetch contacts using three different ways. One of them involves the use of the picker view controller.
- Access fetched contact properties and format them appropriately for display.
- Use the default Contacts UI for picking, viewing and even editing contacts.
- Create a new contact.
I named the demo application Birthdays, as its purpose is to display the birthday date for all the contacts that are imported into the app. The full name, image (if exists) and the home email address will be displayed as well. Ideally, this is supposed to be a birthday reminder, but of course we won’t deal at all with notifications, message sending, and other stuff like this.
The app is navigation-based, and it consists of the following parts:
The ViewController is the default one presented when the app starts. It displays the info I mentioned above for all the imported contacts, and provides options to fetch contacts (right bar button item), create a new one (left bar button item), and view the contact details by tapping on a row:
The contact details will be displayed in the built-in contacts view controller. As you’ll see later, you can either display all of the properties, or just those you’re interested in.
Fetching contacts is going to be a really interesting part in the upcoming sections. I’ll show you three ways in total on how to do that, by using three different approaches:
- In the first one, we’ll be able to fill in a contact name (or just a part of it) and by tapping on the Return button of the keyboard the app will fetch all those contacts matching to the typed name.
- As you can see in the next screenshot, there’s a picker view in the middle of the screen. We’ll use it to find all the contacts that their birthday month matches to the selected month in the picker, and the fetch process will be initiated by tapping on the Done bar button item.
- We’ll use the default picker view controller given by the framework for viewing and selecting contacts directly. Note that the given available contacts in this controller can be customized, as well as the behavior of the picker view controller. You’ll see how later.
Here’s the picker view controller, displaying only those contacts that have a valid birthday date set:
The last part of our app regards the creation of a new contact. This is an easy task, and for the sake of the demo app we’ll use the following view controller to enter the first and last name, the home email address and the birthday of the contact that will be created (we won’t deal with the image, it’s not that important right now).
The sample data (sample contacts) for this demo application is going to be the default contacts that the Simulator database contains. Those contacts are more than enough and good for our purposes. Of course, you can use your own contacts in your device, or add new ones in the Simulator. By default the Simulator contacts do not have an image, but you can easily add one from the photos library in it.
As usually, download the starter project that will be the entry point to the work we’ll do next. Once you get it, open it and take a walk around to see the existing stuff that I’ve already added there. When you feel ready, continue to the next part.
The Contact Store Class
One of the fundamental classes that you’ll be always using when dealing with contacts, is the CNContactStore class. This one actually represents the contacts database that exists in a device, and it’s responsible for managing all the communication between an app and the actual database. More specifically, it handles all the work regarding the fetching, saving and updating of contact and group records. In short, it’s the starting point for the most of the tasks that can be done when working with contacts, and that’s something that we’ll see in the code stuff we’re about to write next.
Besides that, and as I’ve already mentioned in the introduction, user privacy consists of an important aspect in iOS, so you should be always careful when messing with that. It’s well-known that users can either allow or deny access to their contacts in third-party apps, so it’s vital to make sure that your app is authorized to perform contact related tasks at any given moment. Using the CNContactStore class, you can check the current authorization status of your app, and then proceed appropriately. Keep in mind that users can prohibit an application from accessing their contacts database whenever they want so through the Settings app, even if they had initially allowed access, so it’s highly important to ensure that your tasks can be performed or not, and of course to lead your application towards the right direction in each case. Not handled situations like that always end up to bad user experience, and that’s something you should definitely avoid. In this tutorial we’ll take under serious consideration the authorization status of our demo app, starting even from this part. What we’ll do next, is something that you can freely use in your projects as well if you wish so.
As you’re going to find out soon, the contacts store class is handy in the following situations (among others):
- When fetching contacts
- When creating a new contact (saving), and updating one
- When using the Contact Picker view controller to pick contacts
With that in mind, we’ll initialize just one CNContactStore object, and we’ll use it throughout our classes. On the other hand, we could create new such objects every time we need them, but as that class represents the contacts database in code, what’s the reason to have multiple instances of it? So, let’s get started, and first of all open the AppDelegate.swift file to declare and initialize a CNContactStore property. At the top of the class, add the following line:
var contactStore = CNContactStore()
Necessarily, import the following framework right above the class:
import Contacts
Great! Now, and before we deal with the authorization status of the app and all the actions we can take regarding it, let’s write two easy and convenient methods. Note that none of them is required in order to proceed, and we can do our work even without them. However, implementing small methods for specific purposes can be proved really-really handy.
So, the first one is going to make the access to the application delegate (AppDelegate) from any other class a bit easier. Normally, the following command is needed in order the application delegate to be accessed programmatically:
UIApplication.sharedApplication().delegate as! AppDelegate
However, I personally find it a bit disturbing to write all that every time I need to get the app delegate. What if we would write the next class method?
class func getAppDelegate() -> AppDelegate {
return UIApplication.sharedApplication().delegate as! AppDelegate
}
With it, we can access any property and method of the app delegate in a much simpler way. For example, we can get the contacts store property from any class in the project as shown next:
AppDelegate.getAppDelegate().contactStore
The second convenient method that we’ll add in the AppDelegate.swift file is going to display an alert controller with a message that we’ll provide as a parameter each time we use it. The implementation isn’t difficult, however there’s something special here; an alert controller must be presented by a view controller, and the app delegate is not a view controller. To counterattack this, it’s necessary first to find the top most view controller that is currently presented in the app window, and then present the alert controller in that view controller. Here we go:
func showMessage(message: String) {
let alertController = UIAlertController(title: "Birthdays", message: message, preferredStyle: UIAlertControllerStyle.Alert)
let dismissAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (action) -> Void in
}
alertController.addAction(dismissAction)
let pushedViewControllers = (self.window?.rootViewController as! UINavigationController).viewControllers
let presentedViewController = pushedViewControllers[pushedViewControllers.count - 1]
presentedViewController.presentViewController(alertController, animated: true, completion: nil)
}
Now let’s do something really important, and let’s handle the authorization status of the app. This status is represented by the CNAuthorizationStatus enum programmatically and belongs to the CNContactStore class. It contains the following four values:
- NotDetermined: It’s the state where the user hasn’t allowed or denied access to the contacts database yet. The app is in this state when it’s first installed in a device.
- Restricted: In this state the application not only cannot access the contacts data, but the user is also unable to change this option through the Settings. This state is the results of other restrictions that might be active (i.e. Parental control).
- Denied: When the authorization status has this value, then the user has chosen not to allow access to the contacts data. It’s something that can be changed by the user only.
- Authorized: This is the desired situation for every application. When it has this status value, then it can freely access the contacts database and perform tasks using the contacts data.
There’s one thing that must become clear here: The first time (and only the first time) that the user will try to perform an action to the contacts data (for example, to fetch contacts) after the app installation, iOS will display a predefined alert controller asking from the user to authorize the app:
If the user allows access, everything is just fine. However, if the user denies access, then no action will be possible to be taken by the app over the contacts data. In our demo application, and in that specific situation, we’ll display a custom alert message (using the function we implemented before) telling the user that he must grant access to the contacts data in Settings. We will handle this situation inside a new function that we are about to implement right next. Obviously, we’ll take into consideration all the possible authorization status values in that function. Let’s see it first, and then we’ll say a few more words about it:
func requestForAccess(completionHandler: (accessGranted: Bool) -> Void) {
let authorizationStatus = CNContactStore.authorizationStatusForEntityType(CNEntityType.Contacts)
switch authorizationStatus {
case .Authorized:
completionHandler(accessGranted: true)
case .Denied, .NotDetermined:
self.contactStore.requestAccessForEntityType(CNEntityType.Contacts, completionHandler: { (access, accessError) -> Void in
if access {
completionHandler(accessGranted: access)
}
else {
if authorizationStatus == CNAuthorizationStatus.Denied {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let message = "\(accessError!.localizedDescription)\n\nPlease allow the app to access your contacts through the Settings."
self.showMessage(message)
})
}
}
})
default:
completionHandler(accessGranted: false)
}
}
By just looking at the above function, you realize that it contains a completion handler that is called with the true value when access has been granted to the app, and false in the opposite case. Some cases are simple, like the Authorized or the Restricted one, where it’s clear what the value of the completion handler should be. However, the interesting here is that both the Denied and NotDetermined status values are handled in the same case, and for both of them the requestAccessForEntityType:completionHandler: is called, so the app requests for access. For the Denied case only the custom message I said about earlier is going to be displayed.
Note that both the requestAccessForEntityType:completionHandler: and the authorizationStatusForEntityType: methods expect a CNEntityType argument. This is an enum, and it contains just one value named Contacts. That enum is actually specifying the entity which we request access for.
The above function is going to be used multiple times from now on, starting from the next part. We’ll be using it every time we’re about to perform an action regarding the contacts data, so we make sure that it’s allowed to proceed or not, and of course to handle each possible situation so as to avoid a bad user experience. For now we’re just fine,as we’ve prepared some reusable code that will be proved handy several times as we move forward.
Fetching Contacts Using Predicates
As I’ve already explained in the app overview section of the tutorial, we are going to implement three different ways to fetch contacts data. One of them is by writing into a textfield a part of, or the whole name of the contact (or contacts) we want to fetch (regardless if it’s the first or last name), and then ask the Contacts framework for results. This is where we’ll start from, and the key function for doing that is the unifiedContactsMatchingPredicate:keysToFetch:error:.
That function, part of the CNContactStore class, expects two important arguments:
- Predicate: A NSPredicate object which actually works as a filter for the returned results. It’s important to highlight here that only predicates from the CNContact class are accepted, and not generic predicates that you can create on your own (see here). Among all the supported predicate functions of the CNContact class, there’s one called predicateForContactsMatchingName:, and we’ll make use of it.
- keysToFetch: By setting this argument, you specify the partial contacts data you want to fetch. It’s an array with string values that describe the properties of the contacts (CNContact objects) that should be fetched. The framework provides predefined constant string values that can be used as keys.
Note that this method can return an exception, so it must be called in a do-catch statement using the try keyword. The error case is handled in the catch case of the statement.
The result of the unifiedContactsMatchingPredicate:keysToFetch:error: function is an array containing CNContact objects that match to the given predicate, or nil in case something went really bad.
With all that in mind, it’s time to continue with the implementation. This time open the AddContactViewController.swift file, and go straight right above the class opening. Import the Contacts framework here too, as we won’t be able to do anything without it:
import Contacts
Let’s head to the textFieldShouldReturn: delegate method now. Initially we’ll make use of the last function we created in the application delegate, and we’ll check if the app has access so it can continue:
func textFieldShouldReturn(textField: UITextField) -> Bool {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
}
}
return true
}
In case of granted access, we’ll prepare the predicate and the keys we want to be fetched for the matching contacts. Along with them, we’ll declare a couple more variables: An array to store the results (if they exist), and a string variable in which we’ll store a custom message if no matching contacts found, or if the fetching process fails:
func textFieldShouldReturn(textField: UITextField) -> Bool {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let predicate = CNContact.predicateForContactsMatchingName(self.txtLastName.text!)
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactBirthdayKey]
var contacts = [CNContact]()
var message: String!
}
}
return true
}
Just note how we specify the predicate and the keys array, and then let’s proceed. In the next step, we’ll try to fetch the contacts data, and if this action is successful, the contacts array that we just initialized will be populated with values. If no contacts are found or if the fetching fails, we’ll set a custom message that we’ll display right next; with that our implementation in this function will be almost finished:
func textFieldShouldReturn(textField: UITextField) -> Bool {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let predicate = CNContact.predicateForContactsMatchingName(self.txtLastName.text!)
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
var contacts = [CNContact]()
var message: String!
let contactsStore = AppDelegate.getAppDelegate().contactStore
do {
contacts = try contactsStore.unifiedContactsMatchingPredicate(predicate, keysToFetch: keys)
if contacts.count == 0 {
message = "No contacts were found matching the given name."
}
}
catch {
message = "Unable to fetch contacts."
}
if message != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
AppDelegate.getAppDelegate().showMessage(message)
})
}
else {
}
}
}
return true
}
As you see, we left an else case not handled for now, but we’ll revisit it in a while to add the missing code. The important thing here was to see how we can fetch contacts data that matches to a given name, and how we handle situations other than the expected one.
Displaying Fetched Contacts
In the best case scenario that our fetch action returns matching contacts, then it’s necessary to display them to the tableview of the ViewController class. However, the first step is to let the ViewController class know that contacts have been fetched, as the whole process takes place in the AddContactViewController. The best and easiest approach to that is to use the well-known Delegation pattern. So, let’s continue working towards that direction, and keep filling the gaps in our demo application.
In the AddContactViewController.swift file, right above the class, create the following protocol with one delegate method only:
protocol AddContactViewControllerDelegate {
func didFetchContacts(contacts: [CNContact])
}
By using the above delegate method, not only we’ll let the ViewController class know that contacts have been fetched, but also we’ll pass to it the newly fetched contacts.
Then, in the AddContactViewController class add the next delegate declaration:
var delegate: AddContactViewControllerDelegate!
As you recall, we’ve left an else case empty in the textFieldShouldReturn: method in the last part, and now it’s time to add what is missing. Actually, there are only two lines missing: In the first one we’ll call the delegate method we just declared above, and in the second we’ll pop the view controller from the navigation controller stack.
func textFieldShouldReturn(textField: UITextField) -> Bool {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
...
if message != nil {
...
}
else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.didFetchContacts(contacts)
self.navigationController?.popViewControllerAnimated(true)
})
}
}
}
return true
}
As you see, we always use the main thread when we have to deal with the UI. This is an important detail that you shouldn’t forget about, otherwise the UI won’t be updated to the proper time, and you’ll be against an unexpected behavior of the app.
At this point we’re ready to switch to the ViewController.swift file, and handle the fetch results there. Initially, once again we must import the Contacts framework in this class too:
import Contacts
Next, we need to adopt our new custom protocol, therefore you need to add its name at the end of the class header line:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, AddContactViewControllerDelegate
Now, it’s necessary to declare an array of CNContact objects. This one will hold the contacts that are returned from all the fetch requests, and even more, it will be the datasource of our tableview. So, at the top of the ViewController class add the following line:
var contacts = [CNContact]()
Along with that, we must update the number of rows that the tableview will display as shown next:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts.count
}
Before we implement the delegate method we previously declared, it’s necessary to set the ViewController class as the delegate of the AddContactViewControllerDelegate protocol. This will happen in the prepareForSegue: function:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if identifier == "idSegueAddContact" {
let addContactViewController = segue.destinationViewController as! AddContactViewController
addContactViewController.delegate = self
}
}
}
And finally, we must implement our custom delegate method. In it we’ll get all the returned contacts one by one and we’ll append them to the contacts array. At the end of course, we’ll reload the tableview so it displays the new contacts.
func didFetchContacts(contacts: [CNContact]) {
for contact in contacts {
self.contacts.append(contact)
}
tblContacts.reloadData()
}
Let’s display our contact information now. In each cell we’ll show the first and last name of the contact, the birthday date if exists or a short message if it doesn’t, the image data, and the home email if also exists. The implementation you’ll see right next is going to be modified a bit later, but it’s good enough to let you understand how a contact’s properties can be accessed. So, let’s see it:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("idCellContactBirthday") as! ContactBirthdayCell
let currentContact = contacts[indexPath.row]
cell.lblFullname.text = "\(currentContact.givenName) \(currentContact.familyName)"
// Set the birthday info.
if let birthday = currentContact.birthday {
cell.lblBirthday.text = "\(birthday.year)-\(birthday.month)-\(birthday.day)"
}
else {
cell.lblBirthday.text = "Not available birthday data"
}
// Set the contact image.
if let imageData = currentContact.imageData {
cell.imgContactImage.image = UIImage(data: imageData)
}
// Set the contact's home email address.
var homeEmailAddress: String!
for emailAddress in currentContact.emailAddresses {
if emailAddress.label == CNLabelHome {
homeEmailAddress = emailAddress.value as! String
break
}
}
if homeEmailAddress != nil {
cell.lblEmail.text = homeEmailAddress
}
else {
cell.lblEmail.text = "Not available home email"
}
return cell
}
Let’s go through the above implementation. At first, we set the full name label text by concatenating the first and last name strings. In a while I’ll show you a different way to do that, but for now we stick to that. Next, we set the birthday info. If there’s birthday data indeed, we just display it using the simplest possible way. Note that this is a temporary approach, and we’ll format the birthday date appropriately in a while. Also, it’s important to know that the birthday data is not a NSDate object. Instead, it’s a NSDateComponents object, which of course can be converted to a NSDate and then to String object.
The next thing we set is the image data. If it doesn’t exist, the only thing you’ll see in its place is the background color of the imgContactImage image view that I set in the custom cell xib file.
Finally, it’s the home email address we have to set. You notice that we use a loop to go through all the email addresses until we find the one we need. That’s happening because the emailAddresses property of the contact contains all the existing email addresses as labeled values (CNLabeledValue) objects. At the end, if the home email address is found we assign it to the respective label, otherwise we set the message you see above.
If you run the app now, and depending on the contact that you select by typing the name, the above implementation might work, but also might not. In the second case the app will crash, but you don’t have to worry about that. That’s something that we’ll fix. I intentionally didn’t give you the final implementation of the above method, as by going that way it’s easier to present how everything works.
Refetching Contacts
The reason that the app may crash if you run it as is lies to the fact that it’s possible not all values to be fetched when you ask for the contacts data. For that purpose, the CNContact class contains a method named isKeyAvailable: which must be used prior to accessing any contact property. For example, we should have added the following checks before we try to display the birthday date, the image data and the email addresses:
if currentContact.isKeyAvailable(CNContactBirthdayKey) {
...
}
if currentContact.isKeyAvailable(CNContactImageDataKey) {
...
}
if currentContact.isKeyAvailable(CNContactEmailAddressesKey) {
...
}
In case a key is not found, then the proper action that must be taken is to refetch the contact’s data and try to display it again. This is something we’ll do here, and more specifically we’ll create a new function in the ViewController class. However, right before we do that, let’s fix a bit the display of the contact details by including the isKeyAvailable: method. Actually, instead of having three different conditions for the above properties, we’ll create just one condition checking for unavailable keys in all of them, and in case something is missing we’ll call the function that we’ll implement so as to refetch the contact’s data. I intentionally don’t include the contact name keys, as we’re going to see something more about them in the next part.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("idCellContactBirthday") as! ContactBirthdayCell
let currentContact = contacts[indexPath.row]
cell.lblFullname.text = "\(currentContact.givenName) \(currentContact.familyName)"
if !currentContact.isKeyAvailable(CNContactBirthdayKey) || !currentContact.isKeyAvailable(CNContactImageDataKey) || !currentContact.isKeyAvailable(CNContactEmailAddressesKey) {
refetchContact(contact: currentContact, atIndexPath: indexPath)
}
else {
// Set the birthday info.
if let birthday = currentContact.birthday {
cell.lblBirthday.text = "\(birthday.year)-\(birthday.month)-\(birthday.day)"
}
else {
cell.lblBirthday.text = "Not available birthday data"
}
// Set the contact image.
if let imageData = currentContact.imageData {
cell.imgContactImage.image = UIImage(data: imageData)
}
// Set the contact's work email address.
var homeEmailAddress: String!
for emailAddress in currentContact.emailAddresses {
if emailAddress.label == CNLabelHome {
homeEmailAddress = emailAddress.value as! String
break
}
}
if homeEmailAddress != nil {
cell.lblEmail.text = homeEmailAddress
}
else {
cell.lblEmail.text = "Not available home email"
}
}
return cell
}
The refetchContact:atIndexPath: function called above is the one that we’ll implement right now. Further than that, I think that the condition we added is quite straightforward so you can get the logic. Note that by doing that modification, the app won’t crash any more, even if there are unavailable keys in the results.
Now, let’s see the new function:
func refetchContact(contact contact: CNContact, atIndexPath indexPath: NSIndexPath) {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
do {
let contactRefetched = try AppDelegate.getAppDelegate().contactStore.unifiedContactWithIdentifier(contact.identifier, keysToFetch: keys)
self.contacts[indexPath.row] = contactRefetched
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tblContacts.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
})
}
catch {
print("Unable to refetch the contact: \(contact)", separator: "", terminator: "\n")
}
}
}
}
At first we check if the app is authorized to access the contacts database. Then we specify the keys of the partial results we want to fetch, and then we try to fetch once again the data for the given contact. Note that this time we use a new method to do that; the unifiedContactWithIdentifier:keysToFetch:. Its purpose is to fetch data for a specific contact described by the identifier parameter value. Once the results are back, we replace the contact object in the contacts array with the new one. At the end, we reload the specific row of the tableview.
Feel free to run the app again if you want. Refetching a contact’s data is a task that you’d better always perform in case something is missing, so you’re sure that your app won’t have any “surprises” for your users.
Formatting the Output
As you’ve seen so far, we haven’t done anything to properly format the birthday date for each contact right before we display it to the cell. We just concatenated and displayed the birthday date properties, but now that we’ve gone through all the previous important matters it’s time to deal with it.
We’ll fix the birthday date by creating a new custom function in the ViewController class. In it, we’ll use a NSDateFormatter object to convert the date into a localized string, but first, we must convert the date components (the date parts) into a NSDate object. Let’s see this new function:
func getDateStringFromComponents(dateComponents: NSDateComponents) -> String! {
if let date = NSCalendar.currentCalendar().dateFromComponents(dateComponents) {
let dateFormatter = NSDateFormatter()
dateFormatter.locale = NSLocale.currentLocale()
dateFormatter.dateStyle = NSDateFormatterStyle.MediumStyle
let dateString = dateFormatter.stringFromDate(date)
return dateString
}
return nil
}
The parameter of the above method is a date expressed as a NSDateComponents object (in our case the birthday date components). The return value is of course a string. To convert the dateComponents object to a NSDate object, it doesn’t take more than one line of code. Using the NSCalendar class we perform the conversion, and the date object is ready to be handled by the date formatter that is being initialized right next. Setting the current locale to the date formatter is a required action so as to achieve a localized description of the date. Finally, we set the preferred style for the date (not to long, not too short), and we perform the final conversion. The converted value is eventually returned back to the caller.
Let’s fix now the display of the birthday date, simply by calling the above method:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
if !currentContact.isKeyAvailable(CNContactBirthdayKey) || !currentContact.isKeyAvailable(CNContactImageDataKey) || !currentContact.isKeyAvailable(CNContactEmailAddressesKey) {
refetchContact(contact: currentContact, atIndexPath: indexPath)
}
else {
// Set the birthday info.
if let birthday = currentContact.birthday {
cell.lblBirthday.text = getDateStringFromComponents(birthday)
}
...
}
return cell
}
Great. Now the birthday date will be displayed in a much nicer fashion.
Let’s see something interesting regarding the display of the first and last name now. The CNContact class provides built-in formatters that can help us easily format two kind of data: The full name of the contact (CNContactFormatter) and the address (CNPostalAddressFormatter). Here we’re going to use the first one, so the full name is automatically formatted by the Contacts framework.
Initially, let’s go to modify one last time the display of the contacts as shown below:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("idCellContactBirthday") as! ContactBirthdayCell
let currentContact = contacts[indexPath.row]
cell.lblFullname.text = CNContactFormatter.stringFromContact(currentContact, style: .FullName)
...
return cell
}
As you can see, the cell.lblFullname.text = “(currentContact.givenName) (currentContact.familyName)” line has been replaced by this:
cell.lblFullname.text = CNContactFormatter.stringFromContact(currentContact, style: .FullName)
Obviously, we don’t need anymore to concatenate the first (given) and last (family) names so as to create the full name of the contact. The CNContactFormatter does that, and it also produces a localized string (by setting the name parts in the proper order, depending on the locale settings of the device).
However, the above can cause some complications, as the contact formatter needs to access all the keys related to the name of a contact, even those that we didn’t include in the keys to fetch array. However, we don’t have to write all of them one by one. All the related keys are specified by a key descriptor, and the descriptor is used instead of single keys in the keys to fetch array.
To make things specific, go to the AddContactViewController file, in the textFieldShouldReturn: method. In there, replace this line:
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
with the next one where we’re using the key descriptor:
let keys = [CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName), CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
The way the descriptor is formatted is quite specific, exactly as it’s shown above. Besides that, the rest of the keys remain intact.
The above change must be performed in the refetchContact: function (in the ViewController class) as well. All you have to do is to replace the keys array definition with the above line, so go for it:
func refetchContact(contact contact: CNContact, atIndexPath indexPath: NSIndexPath) {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let keys = [CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName), CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
...
}
}
}
And with that, we’ve managed to do all the formatting-related modifications in our code. Of course, you can still use single keys for fetching single name values, but that’s always up to your requirements.
Fetching Contacts Using Custom Filters
One of the first things I presented in this tutorial was how to fetch contacts using predicates. We used a predicate from the Contacts framework to get contacts matching to a given name, but if you remember, there’s a drawback generally in this method; we have to use only the framework’s built-in predicates and we cannot create our own. So the question here is, how can we fetch contacts by applying custom filters?
This question can become more detailed for our demo application, so we could ask ourselves, how can we fetch contacts based on their birth month? In the AddContactViewController class there’s a picker view that displays all the months, so what we want to be able to do now, is to pick a month, then tap on the Done button, and eventually get all those records where the birthday month equals to the picked month.
Well, as you guess, there’s a solution to “apply” custom filters, however the whole process is a bit more manual than using predicates. In general, the approach we’ll see is based on the enumerateContactsWithFetchRequest(_:usingBlock) method of the CNContactStore class that Apple recommends to use in such cases. This method fetches all contacts, so custom criteria can be set in the block part (closure) of it by comparing property values or applying any other custom logic, and eventually keeping only those contacts that you really need.
In our case we are going to check for two things: At first, we must make sure that the birthday of each contact has been set so we avoid any unwanted crash. At second, we’ll just compare the birthday month to the picked month in the picker view, and if there’s a match we’ll keep the contact in an array. Doing that is pretty easy, as the birthday is expressed as a NSDateComponents object, so we can access the month directly. Further than that, the rest is easy: All the stuff we’ll see has already been presented in the previous parts, and I’ve already talked about it. We are going to write the new code here in the performDoneItemTap custom method of the AddContactViewController class, as we want to fetch contacts based on the month only when the Done button of the view controller is tapped.
Here you are:
func performDoneItemTap() {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
var contacts = [CNContact]()
let keys = [CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName), CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
do {
let contactStore = AppDelegate.getAppDelegate().contactStore
try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) { (contact, pointer) -> Void in
if contact.birthday != nil && contact.birthday!.month == self.currentlySelectedMonthIndex {
contacts.append(contact)
}
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate.didFetchContacts(contacts)
self.navigationController?.popViewControllerAnimated(true)
})
}
catch let error as NSError {
print(error.description, separator: "", terminator: "\n")
}
}
}
}
As you can see, upon completion we call the delegate so the tableview in the ViewController class to be updated with the new contact data, and then we pop the view controller. The above can become really useful to you in many cases, as the only thing you have to do is to change the condition that sets the filter criteria in the above block.
The Contact Picker View Controller
All the contact management and the work that we’ve done so far was totally programmatic, however the story doesn’t end here. The Contacts framework provides view controllers (UI) for direct, visual access to the contacts and immediate interaction with them. The provided view controllers are similar to those in the Contacts app, so you’re provided with a picker controller to select a contact (or contacts), a view controller for viewing contact details, and the sheet to edit them. Overriding the default behavior is allowed when picking contacts, and there are delegate methods that let you handle the results.
In this part we’re going to see how the picker view controller can be used to select and import contacts in our app. No much preparation is required, however the customization level depends on the needs of each app. The Contacts framework allows to set three predicates optionally that let you limit the displayed contacts and change the default behavior:
- predicateForEnablingContact: This is probably the predicate that you’ll be using the most. With it, you can specify which contacts should be available in the picker controller. For example, you can filter the contacts that way, so only those with a valid birthday value to be available for picking.
- predicateForSelectionOfContact: With this, you can dictate the picker view controller under what conditions it should return the selected contact when it’s selected, and when it should display the details view controller for additional selection.
- predicateForSelectionOfProperty: Using it, you specify whether the default action of a property should be performed (like for example to make a phone call when tapping on a phone number), or the tapped property should be returned.
Here we’re going to use the first predicate only, asking from the picker view controller to make available only the contacts that have an existing birthday date. Using the other two isn’t difficult, but we’re not going to need them here; for your reference I prompt you to the respective documentation.
Back to our application again, open the AddContactViewController.swift file. Initially, go to the top of it, and import the ContactsUI framework:
import ContactsUI
Next, adopt the CNContactPickerDelegate protocol, so we can handle the returned contacts:
class AddContactViewController: UIViewController, UITextFieldDelegate, UIPickerViewDelegate, CNContactPickerDelegate
From now on our work will take place in the showContacts: IBAction method. This one will enable the button at the bottom side of the AddContactViewController. Let’s see the implementation:
@IBAction func showContacts(sender: AnyObject) {
let contactPickerViewController = CNContactPickerViewController()
contactPickerViewController.predicateForEnablingContact = NSPredicate(format: "birthday != nil")
contactPickerViewController.delegate = self
presentViewController(contactPickerViewController, animated: true, completion: nil)
}
As simple as that! In this demo application we won’t show the details card when tapping on a contact. If, however, that’s something you want to do in your apps, then it’s easy to control the properties that will be displayed in the details. All you have to do is to assign an array with the desired keys to a property named displayedPropertyKeys. For example, if we were about to show the details card in our application, then we should have added the next line before we present the picker view controller:
contactPickerViewController.displayedPropertyKeys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
Just a few minutes ago we adopted the CNContactPickerDelegate protocol, and now it’s time to implement a required delegate method. In it, we’ll get the selected contact, and we’ll send it through our own delegate method back to the ViewController class.
func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) {
delegate.didFetchContacts([contact])
navigationController?.popViewControllerAnimated(true)
}
In case you display the details contact card and you want to handle a returned property, you should use the contactPicker:didSelectContactProperty: delegate method. We won’t implement it here, as we don’t need it. You can find a collection of all the delegate methods here.
The app now can be tested again. This time use the “Open contacts to select” button to present the picker view controller. You’ll notice that the contacts that don’t have a valid birthday value are not available. Select a contact, and you’ll see it being displayed in the tableview of the ViewController.
The Contacts View Controller
So far we’ve implemented three different ways that allow us to fetch contacts and add them to our app. However, just displaying them in a tableview is not good enough; we want something more than that, and that is to display a selected contact details in a new view controller. Actually, we won’t create a custom one, but we’ll use the contact view controller provided by the Contacts framework. Through it not only we’ll be able to view the contact’s data, but to edit it as well. Of course, this is given for free by the CNContactViewController class.
Let’s go back to the ViewController.swift file, and let’s handle the situation where the user taps on a contact row. Before we present an instance of the CNContactViewController however, we need to make sure that all the keys for the selected contact details are actually available. Even though we check for available keys when presenting each cell and even though we refetch contacts if needed, we still can’t tell for sure if the user’s tap on the row will be faster than the refetch action. So, it’s something that must be handled.
Previously, we used the isKeyAvailable: method of the CNContact class to check the availability of a key in a fetched contact. In addition to that method, the CNContact class offers one more called areKeysAvailable:, which we’ll use to make sure that all the keys needed by the contacts view controller exist. This method accepts just one parameter, an array of keys or key descriptors (just like the keys array we used many times to fetch contacts). In the case of the CNContactViewController though we must set the CNContactViewController.descriptorForRequiredKeys() specific value as the only item of the parameter array, as that class method will automatically check for all the existing keys. In case the keys are available, we’ll present the contacts view controller. In the opposite case, we’ll act similarly to what we previously did, and we’ll refetch the contact using the descriptorForRequiredKeys() for specifying the keys that should be fetched.
Furthermore, the keys array that we used to fetch contact data throughout this demo app will become handy once again. Not to check for availability as I just explained, but to specify which properties should be displayed in the contacts view controller. You’ll see how it can be used in the following implementation. As a side note, keep in mind that if you omit this property, then all the existing contact’s properties (and not just those we want to show) will be displayed in the contacts view controller.
Having said all the above, we’re ready to see the respective code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedContact = contacts[indexPath.row]
let keys = [CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName), CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey]
if selectedContact.areKeysAvailable([CNContactViewController.descriptorForRequiredKeys()]) {
let contactViewController = CNContactViewController(forContact: selectedContact)
contactViewController.contactStore = AppDelegate.getAppDelegate().contactStore
contactViewController.displayedPropertyKeys = keys
navigationController?.pushViewController(contactViewController, animated: true)
}
else {
AppDelegate.getAppDelegate().requestForAccess({ (accessGranted) -> Void in
if accessGranted {
do {
let contactRefetched = try AppDelegate.getAppDelegate().contactStore.unifiedContactWithIdentifier(selectedContact.identifier, keysToFetch: [CNContactViewController.descriptorForRequiredKeys()])
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let contactViewController = CNContactViewController(forContact: contactRefetched)
contactViewController.contactStore = AppDelegate.getAppDelegate().contactStore
contactViewController.displayedPropertyKeys = keys
self.navigationController?.pushViewController(contactViewController, animated: true)
})
}
catch {
print("Unable to refetch the selected contact.", separator: "", terminator: "\n")
}
}
})
}
}
In the above snippet you can see that we specify the properties we want to be displayed simply by using the displayedPropertyKeys property of the contacts view controller instance. Another detail that worths to be mentioned, is that we provide our contact store instance to the contacts view controller through the contactStore property. This is not mandatory to be done if there’s not an existing CNContactStore instance in the app, as the CNContactsViewController will create a new one automatically. All the rest have been already discussed. As a last step, don’t forget to import the next framework at the top of the file:
import ContactsUI
Creating and Saving a New Contact
So far we’ve seen many new things regarding the new Contacts framework. However, there’s still one part of it that we haven’t discussed about, and that is how to create a new contact programmatically and store it to the database. So, as you understand, in this last part of the tutorial we’re about to cover this subject too. I won’t go into the details of updating an existing record, as it’s a task similar to what we’ll see here, so I leave it to you entirely to find the differences between the two processes.
Besides the CNContact class that represents a single contact and all of its properties, the Contacts framework provides one more, named CNMutableContact. As it’s clear from its name, this class is similar to the first one; however, this one allows to assign new values to the properties of a contact, and therefore create a new one or update an existing contact. The actual saving (and updating) process is handled from our well-known contact store (CNContactStore) class, but that’s the last step when creating new contacts. You’ll see the exact details right next.
Generally, setting values in the properties of a contact using the CNMutableContact class consists of the opposite action than when getting them. That means that further than the simple properties that are assigned directly with a single value (i.e. the first name), special properties must have special treatment. For example:
- When setting the birthday date of a contact, a NSDateComponents object must be created and assigned to the respective property
- When setting the contact image, then a NSData object must be assigned to it
- When setting email addresses, a CNLabeledValue object must be created for each single email address, and then all of them should be assigne to the emailAddresses property as an array.
The above are just some examples. There are more contact properties of course that should be carefully treated, but no matter what, doing so isn’t a difficult task as you’ll see right next.
Back to our demo app again, this time we’ll switch to the CreateContactViewController.swift file. In this, you’ll find an empty custom function named createContact(), and it’s the place that all of our work will take place here. Simply, we’ll create a new instance of the CNMutableContact class, then we’ll set values to all the properties we’re interested in, and finally we’ll store the new record to the database using the contact store. Let’s see the implementation:
func createContact() {
let newContact = CNMutableContact()
newContact.givenName = txtFirstname.text!
newContact.familyName = txtLastname.text!
let homeEmail = CNLabeledValue(label: CNLabelHome, value: txtHomeEmail.text!)
newContact.emailAddresses = [homeEmail]
let birthdayComponents = NSCalendar.currentCalendar().components([NSCalendarUnit.Year, NSCalendarUnit.Month, NSCalendarUnit.Day], fromDate: datePicker.date)
newContact.birthday = birthdayComponents
do {
let saveRequest = CNSaveRequest()
saveRequest.addContact(newContact, toContainerWithIdentifier: nil)
try AppDelegate.getAppDelegate().contactStore.executeSaveRequest(saveRequest)
navigationController?.popViewControllerAnimated(true)
}
catch {
AppDelegate.getAppDelegate().showMessage("Unable to save the new contact.")
}
}
Taking things from the beginning, the first step is to initialize a CNMutableContact object that is being used all the way next. It’s obvious that setting the first and last name properties is an easy task. The home email address that is coming next must be created as a CNLabeledValue object, and the way that happens is demonstrated right above. Once the new email address has been created, it’s being added to the emailAddresses property as part of an array with email addresses. In our case of course, we don’t have any other addresses. Lastly, we specify the birthday date of the new contact, based on the picked by the user date. Creating a NSDateComponents object from a NSDate object is extremely easy using the NSCalendar class exactly as shown above. Note of how the calendar units (year, month, day) are combined together so they produce the final desired value.
The most interesting part in the given code snippet is the way a new contact is saved. As you can notice, it’s necessary to create a CNSaveRequest object first, and then to add the new contact object to it. Up to that point no actual saving has been performed. That happens right next, where the executeSaveRequest: method of the contact store instance is called.
In case that the new contact cannot be saved, an alert with a message is just being presented to the user.
Run the app now and use the left bar button item in the ViewController to create a new contact. Save your record, and then go and search for it using any of the methods we implemented in the previous parts.
Important Note: I noticed in my tests during the writing time of this tutorial that when creating a new record and saving it to the contacts database, accessing the contact details through the app (by tapping on a contact row) wasn’t possible any more. Instead, the following message was displayed in the console:
[CNUI ERROR] error calling service – Couldn’t communicate with a helper application.
There’s no much available help online, further than this has been reported as a bug to Apple. Keep that in mind, just in case you want to avoid creating a new contact contact while testing the app.
Summary
Reaching the end of this tutorial, I hope that I’ve managed to make clear how easy it is to work with the new Contacts framework. If you had used the AddressBook API in the past, then you can confirm that everything you’ve seen here consists of a huge change when dealing with contacts. Needless to say that you can play with the demo app as much as you can, modify it and expand it the way you want. There’s always room for improvements, but never forget about the user privacy and that you must respect the user’s choice regarding the app access to the contacts. Don’t miss the official documentation, you’ll find interesting things there too. I wish you enjoyed and found this tutorial useful; until next time, have great and productive days!
For reference, you can download the complete Xcode project here.