Swift iOS9 New Contacts Framework - How to Retrieve Only Cncontact That Has a Valid Email Address

Swift Using Contacts Framework, search using phone number to get Name and User Image

In order to get this example up-and-running quickly I used the following sources of info:

Filter non-digits from string

https://stackoverflow.com/a/32700339/558933

http://www.appcoda.com/ios-contacts-framework/

The code block below includes the authorisation check because I had to get it working in order to test in the simulator. The code is just the Single-View Apps view controller and you can connect up a UIButton in the Storyboard to the findContactInfoForPhoneNumber: method to get if to run. Output is to the console - you will need to replace these print statements with something else.

If you are not interested in the full view controller code then just look at the searchForContactUsingPhoneNumber(phoneNumber: String) method. I've followed Apple's advice in the docs to run the CNContact framework asynchronously.

The code strips all the +, - and ( symbols that could be in a phone number and just matches the digits so the phone number you pass in to match MUST be exactly the same.

//
// ViewController.swift
// ContactsTest
//
// Created by Robotic Cat on 13/04/2016.
//

import UIKit
import Contacts

class ViewController: UIViewController {

// MARK: - App Logic
func showMessage(message: String) {
// Create an Alert
let alertController = UIAlertController(title: "Alert", message: message, preferredStyle: UIAlertControllerStyle.Alert)

// Add an OK button to dismiss
let dismissAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (action) -> Void in
}
alertController.addAction(dismissAction)

// Show the Alert
self.presentViewController(alertController, animated: true, completion: nil)
}

func requestForAccess(completionHandler: (accessGranted: Bool) -> Void) {
// Get authorization
let authorizationStatus = CNContactStore.authorizationStatusForEntityType(CNEntityType.Contacts)

// Find out what access level we have currently
switch authorizationStatus {
case .Authorized:
completionHandler(accessGranted: true)

case .Denied, .NotDetermined:
CNContactStore().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)
}
}

@IBAction func findContactInfoForPhoneNumber(sender: UIButton) {

self.searchForContactUsingPhoneNumber("(888)555-1212)")
}

func searchForContactUsingPhoneNumber(phoneNumber: String) {

dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), { () -> Void in
self.requestForAccess { (accessGranted) -> Void in
if accessGranted {
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactImageDataKey, CNContactPhoneNumbersKey]
var contacts = [CNContact]()
var message: String!

let contactsStore = CNContactStore()
do {
try contactsStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) {
(contact, cursor) -> Void in
if (!contact.phoneNumbers.isEmpty) {
let phoneNumberToCompareAgainst = phoneNumber.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("")
for phoneNumber in contact.phoneNumbers {
if let phoneNumberStruct = phoneNumber.value as? CNPhoneNumber {
let phoneNumberString = phoneNumberStruct.stringValue
let phoneNumberToCompare = phoneNumberString.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("")
if phoneNumberToCompare == phoneNumberToCompareAgainst {
contacts.append(contact)
}
}
}
}
}

if contacts.count == 0 {
message = "No contacts were found matching the given phone number."
}
}
catch {
message = "Unable to fetch contacts."
}

if message != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.showMessage(message)
})
}
else {
// Success
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Do someting with the contacts in the main queue, for example
/*
self.delegate.didFetchContacts(contacts) <= which extracts the required info and puts it in a tableview
*/
print(contacts) // Will print all contact info for each contact (multiple line is, for example, there are multiple phone numbers or email addresses)
let contact = contacts[0] // For just the first contact (if two contacts had the same phone number)
print(contact.givenName) // Print the "first" name
print(contact.familyName) // Print the "last" name
if contact.isKeyAvailable(CNContactImageDataKey) {
if let contactImageData = contact.imageData {
print(UIImage(data: contactImageData)) // Print the image set on the contact
}
} else {
// No Image available

}
})
}
}
}
})
}

}

How to fetch all contacts record in iOS 9 using Contacts Framework

Both other answers do only load contacts from the container with the defaultContainerIdentifier. In a scenario, where the user has more than one container (i.e. an Exchange and an iCloud account which both are used to store contacts), this would only load the contacts from the account that is configured as the default. Therefore, it would not load all contacts as requested by the author of the question.

What you'll probably want to do instead is getting all the containers and iterate over them to extract all contacts from each of them. The following code snippet is an example of how we do it in one of our apps (in Swift):

lazy var contacts: [CNContact] = {
let contactStore = CNContactStore()
let keysToFetch = [
CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName),
CNContactEmailAddressesKey,
CNContactPhoneNumbersKey,
CNContactImageDataAvailableKey,
CNContactThumbnailImageDataKey]

// Get all the containers
var allContainers: [CNContainer] = []
do {
allContainers = try contactStore.containersMatchingPredicate(nil)
} catch {
print("Error fetching containers")
}

var results: [CNContact] = []

// Iterate all containers and append their contacts to our results array
for container in allContainers {
let fetchPredicate = CNContact.predicateForContactsInContainerWithIdentifier(container.identifier)

do {
let containerResults = try contactStore.unifiedContactsMatchingPredicate(fetchPredicate, keysToFetch: keysToFetch)
results.appendContentsOf(containerResults)
} catch {
print("Error fetching results for container")
}
}

return results
}()

Contact Framework phone array

The answer had nothing to do with swift or Contacts framework but rather with programming 1o1. It must have been late that i did not figure out the solution back than but posting the solution anyway:

// Creating empty array of CNLabeledValues
var phoneNumbers : [CNLabeledValue] = []

// Add the mobile number to the array:
if let mobiel = contact.nummer {
phoneNumbers.append(CNLabeledValue(label: CNLabelPhoneNumberMobile, value: CNPhoneNumber(stringValue: mobiel)))
}

// Add the Main number to the array:
if let huisnummer = contact.huisnummer {
phoneNumbers.append(CNLabeledValue(label: CNLabelPhoneNumberMain, value: CNPhoneNumber(stringValue: huisnummer)))
}

// Add the array to the Contacts Framework contact
cnContact.phoneNumbers = phoneNumbers

Thats it :-)

How to retrieve emails from iPhone Contacts using swift?

Apple Inc. introduce Contacts.framework from iOS 9. So, it would be better use this framework to retrieve contacts.

Here is way to fetch email or whole contact from Contacts app.

Add Contacts.framework to your project.

Create or add new file of type header and give name like yourProjectName-Bridging-Header.h write #import <Contacts/Contacts.h> statement into file and save and set appropriate path of this file from build setting.

Now create method

func getAllContacts() {

let status = CNContactStore.authorizationStatusForEntityType(CNEntityType.Contacts) as CNAuthorizationStatus

if status == CNAuthorizationStatus.Denied {

let alert = UIAlertController(title:nil, message:"This app previously was refused permissions to contacts; Please go to settings and grant permission to this app so it can use contacts", preferredStyle:UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title:"OK", style:UIAlertActionStyle.Default, handler:nil))
self.presentViewController(alert, animated:true, completion:nil)
return
}

let store = CNContactStore()
store.requestAccessForEntityType(CNEntityType.Contacts) { (granted:Bool, error:NSError?) -> Void in

if !granted {

dispatch_async(dispatch_get_main_queue(), { () -> Void in

// user didn't grant access;
// so, again, tell user here why app needs permissions in order to do it's job;
// this is dispatched to the main queue because this request could be running on background thread
})
return
}

let arrContacts = NSMutableArray() // Declare this array globally, so you can access it in whole class.

let request = CNContactFetchRequest(keysToFetch:[CNContactIdentifierKey, CNContactEmailAddressesKey, CNContactBirthdayKey, CNContactImageDataKey, CNContactPhoneNumbersKey, CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.FullName)])

do {

try store.enumerateContactsWithFetchRequest(request, usingBlock: { (contact:CNContact, stop:UnsafeMutablePointer<ObjCBool>) -> Void in

let arrEmail = contact.emailAddresses as NSArray

if arrEmail.count > 0 {

let dict = NSMutableDictionary()
dict.setValue((contact.givenName+" "+contact.familyName), forKey: "name")
let emails = NSMutableArray()

for index in 0...arrEmail.count {

let email:CNLabeledValue = arrEmail.objectAtIndex(index) as! CNLabeledValue
emails .addObject(email.value as! String)
}
dict.setValue(emails, forKey: "email")
arrContacts.addObject(dict) // Either retrieve only those contact who have email and store only name and email
}
arrContacts.addObject(contact) // either store all contact with all detail and simplifies later on
})
} catch {

return;
}
}
}

Call this method where you want self.getAllContacts()

And when you want to retrieve

for var index = 0; index < self.arrContacts.count; ++index {

let dict = self.arrContacts[index] as! NSDictionary
print(dict.valueForKey("name"))
print(dict.valueForKey("email"))
}

How to tell ABPeoplePickerNavigationController to list only contacts that have an email address?

I do not believe there is a way to get iOS to do this filtering. I do it in code too. Note that you need to look for all kinds of email addresses - you have to iterate through the dictionary that you can get. Working with this is a PITA for sure - I've done it before - and you have to be careful to not have memory leaks.

What I do is as you suggest - iterate through all contact myself, then I pop a view with a table and let then select the names of the people they want. I keep an association around so I know what address is associated with what name, then use the system email framework and then populate the send-to addresses.

What's the iOS 9 alternative to CNLabeledValue?

The Contacts framework (CN*) was added in iOS 9. Before that, we used the AddressBook framework. There is no exact equivalent to CNLabledValue there. The closest is ABMultiValue, which is a CoreFoundation type.

Note that the current docs often suggest that some of these types only exist in macOS 12+, but that's not correct. Many of these did exist on older versions of iOS.



Related Topics



Leave a reply



Submit