Get a list of all contacts on iOS
I want to get a list of all contacts of an iPhone.
I checked Address Book
reference, I may missed something but I didn't see it provides a method to get a list of contacts.
Solution 1:
In my original answer, at the end of this answer, I show how to retrieve contacts in iOS versions prior to 9.0 in a manner that addresses some of the problems entailed by other answers here.
But, if only supporting iOS 9 and later, one should use the Contacts
framework, avoiding some of the annoying bridging issues entailed when using the older AddressBook
framework.
So, in iOS 9, you'd use the Contacts
framework:
@import Contacts;
You also need to update your Info.plist
, adding a NSContactsUsageDescription
to explain why your app requires access to contacts.
And then do something like follows:
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
if (status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Access to contacts." message:@"This app requires access to contacts because ..." preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:@"Go to Settings" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:TRUE completion:nil];
return;
}
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
// make sure the user granted us access
if (!granted) {
dispatch_async(dispatch_get_main_queue(), ^{
// 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;
}
// build array of contacts
NSMutableArray *contacts = [NSMutableArray array];
NSError *fetchError;
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactIdentifierKey, [CNContactFormatter descriptorForRequiredKeysForStyle:CNContactFormatterStyleFullName]]];
BOOL success = [store enumerateContactsWithFetchRequest:request error:&fetchError usingBlock:^(CNContact *contact, BOOL *stop) {
[contacts addObject:contact];
}];
if (!success) {
NSLog(@"error = %@", fetchError);
}
// you can now do something with the list of contacts, for example, to show the names
CNContactFormatter *formatter = [[CNContactFormatter alloc] init];
for (CNContact *contact in contacts) {
NSString *string = [formatter stringFromContact:contact];
NSLog(@"contact = %@", string);
}
}];
Below is my answer applicable if supporting iOS versions prior to iOS 9.0.
--
A couple of reactions to not only your question, but also many of the answers provided here (which either fail to request permission, don't handle ABAddressBookCreateWithOptions
errors properly, or leak):
-
Obviously, import the
AddressBook
framework:#import <AddressBook/AddressBook.h>
or
@import AddressBook;
-
You must request permission for the app to access the contacts. For example:
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus(); if (status == kABAuthorizationStatusDenied || status == kABAuthorizationStatusRestricted) { // if you got here, user had previously denied/revoked permission for your // app to access the contacts and all you can do is handle this gracefully, // perhaps telling the user that they have to go to settings to grant access // to contacts [[[UIAlertView alloc] initWithTitle:nil message:@"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; return; } CFErrorRef error = NULL; ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error); if (!addressBook) { NSLog(@"ABAddressBookCreateWithOptions error: %@", CFBridgingRelease(error)); return; } ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) { if (error) { NSLog(@"ABAddressBookRequestAccessWithCompletion error: %@", CFBridgingRelease(error)); } if (granted) { // if they gave you permission, then just carry on [self listPeopleInAddressBook:addressBook]; } else { // however, if they didn't give you permission, handle it gracefully, for example... dispatch_async(dispatch_get_main_queue(), ^{ // BTW, this is not on the main thread, so dispatch UI updates back to the main queue [[[UIAlertView alloc] initWithTitle:nil message:@"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; }); } CFRelease(addressBook); });
-
Note that above, I have not used the pattern suggested by others:
CFErrorRef *error = NULL; ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, error);
That is not correct. As you'll see above, you want:
CFErrorRef error = NULL; ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
The former pattern will not capture the error correctly, whereas the latter will. If
error
was notNULL
, don't forget toCFRelease
it (or transfer ownership to ARC like I did) or else you'll leak that object. -
To iterate through the contacts, you want to:
- (void)listPeopleInAddressBook:(ABAddressBookRef)addressBook { NSArray *allPeople = CFBridgingRelease(ABAddressBookCopyArrayOfAllPeople(addressBook)); NSInteger numberOfPeople = [allPeople count]; for (NSInteger i = 0; i < numberOfPeople; i++) { ABRecordRef person = (__bridge ABRecordRef)allPeople[i]; NSString *firstName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty)); NSString *lastName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonLastNameProperty)); NSLog(@"Name:%@ %@", firstName, lastName); ABMultiValueRef phoneNumbers = ABRecordCopyValue(person, kABPersonPhoneProperty); CFIndex numberOfPhoneNumbers = ABMultiValueGetCount(phoneNumbers); for (CFIndex j = 0; j < numberOfPhoneNumbers; j++) { NSString *phoneNumber = CFBridgingRelease(ABMultiValueCopyValueAtIndex(phoneNumbers, j)); NSLog(@" phone:%@", phoneNumber); } CFRelease(phoneNumbers); NSLog(@"============================================="); } }
-
I want to draw your attention to a fairly key detail, namely the "Create Rule":
Core Foundation functions have names that indicate when you own a returned object:
Object-creation functions that have “
Create
” embedded in the name;Object-duplication functions that have “
Copy
” embedded in the name.
If you own an object, it is your responsibility to relinquish ownership (using CFRelease) when you have finished with it.
This means that you bear responsibility for releasing any object returned by any Core Foundation function with
Create
orCopy
in the name. You can either callCFRelease
explicitly (as I did above withaddressBook
andphoneNumbers
) or, for objects that support toll-free bridging, you can transfer ownership to ARC with__bridge_transfer
orCFBridgingRelease
(as I did above withallPeople
,lastName
,firstName
, andphoneNumber
).The static analyzer (press shift+command+B in Xcode or choose "Analyze" from the "Product" menu) can identify many situations in which you neglected to observe this "Create Rule" and failed to release the appropriate objects. So, whenever writing Core Foundation code like this, always run it through the static analyzer to make sure you don't have any obvious leaks.
Solution 2:
Perhaps ABPerson
function ABAddressBookCopyArrayOfAllPeople might do?
Example:
ABAddressBookRef addressBook = ABAddressBookCreate( );
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople( addressBook );
CFIndex nPeople = ABAddressBookGetPersonCount( addressBook );
for ( int i = 0; i < nPeople; i++ )
{
ABRecordRef ref = CFArrayGetValueAtIndex( allPeople, i );
...
}