Start off by creating a new Swift file called ContactFetchHelper.swift. After creating the file, add it to a new folder called Helpers. First, we'll extract all the contact-fetching code to our ContactFetchHelper struct. Then, we'll refactor ViewController.swift so it uses our new helper instead of implementing all the fetching code in the view controller. The following code shows the implementation for ContactFetchHelper:
import Contacts
struct ContactFetchHelper {
typealias ContactFetchCallback = ([HCContact]) -> Void
let store = CNContactStore()
func fetch(withCallback callback: @escaping
ContactFetchCallback) {
if CNContactStore.authorizationStatus(for: .contacts) == .notDetermined {
store.requestAccess(for: .contacts, completionHandler:
{authorized, error in
if authorized {
self.retrieve(withCallback: callback)
}
})
} else if CNContactStore.authorizationStatus(for: .contacts) == .authorized {
retrieve(withCallback: callback)
}
}
private func retrieve(withCallback callback: ContactFetchCallback) {
let containerId = store.defaultContainerIdentifier()
let keysToFetch =
[CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactImageDataKey as CNKeyDescriptor,
CNContactImageDataAvailableKey as CNKeyDescriptor,
CNContactEmailAddressesKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor,
CNContactPostalAddressesKey as CNKeyDescriptor]
let predicate = CNContact.predicateForContactsInContainer(withIdentifier: containerId)
guard let retrievedContacts = try? store.unifiedContacts(matching:
predicate, keysToFetch: keysToFetch) else {
// call back with an empty array if we fail to retrieve contacts
callback([])
return
}
let contacts: [HCContact] = retrievedContacts.map {
contact in
return HCContact(contact: contact)
}
callback(contacts)
}
}
This simple struct now contains all the required logic to fetch contacts. Let's go through some of the most interesting parts of code in this struct:
typealias ContactFetchCallback = ([HCContact]) -> Void
This line of code defines an alias, named ContactFetchCallback, for a closure that receives an array of HCContact instances and returns nothing. This is the closure that is passed to the fetch method, and it's called after the fetching is performed.
The fetch method is the method we'll call whenever we want to fetch contacts. The only argument it takes is a closure that needs to be called when the contacts are fetched. The fetch method performs the same authorization check we had in the view controller's viewDidLoad method.
Next, we have a private method, retrieve, that actually retrieves the contacts. The fetch method calls this method and passes it the callback it received. Once retrieve has retrieved the contacts, it calls the callback with the array of fetched contacts.
In ViewController.swift, all you need to do is use the following code to retrieve contacts:
let contactFetcher = ContactFetchHelper()
contactFetcher.fetch { [weak self] contacts in
self?.contacts = contacts
self?.collectionView.reloadData()
}
You can delete the retrieveContacts method entirely and the preceding snippet replaces the code that checked for permissions in viewDidLoad. Also, because we're not directly using the Contacts framework anymore, you can remove its import at the top of the file. You have now successfully extracted the contact's fetching code into a struct and you're using a typealias to make your code more readable. This is already a big win for maintainability and reusability. Now, let's extract our animation code as well.