iOS 6 Address Book not working?
Asked Answered
D

4

14

My method of programmatically retrieving e-mail addresses from the Address Book no longer seems to work on iOS 6 devices. It worked in iOS 5 and oddly, still works in the iOS 6 Simulator. Is there a new way to programmatically retrieve contacts from a users' Address Book?

ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);

self.contacts = [[NSMutableArray alloc] init];

int contactIndex = 0;
for (int i = 0; i < nPeople; i++) {
    // Get the next address book record.
    ABRecordRef record = CFArrayGetValueAtIndex(allPeople, i);        

    // Get array of email addresses from address book record.
    ABMultiValueRef emailMultiValue = ABRecordCopyValue(record, kABPersonEmailProperty);
    NSArray *emailArray = (__bridge_transfer NSArray *)ABMultiValueCopyArrayOfAllValues(emailMultiValue);

    [self.contacts addObject:emailArray];
}

To clarify, the above does not crash, it simply returns no results. ABAddressBookCopyArrayOfAllPeople is empty. Thanks!

Dryly answered 20/9, 2012 at 17:2 Comment(0)
T
12

Probably related to the new privacy controls—as of iOS 6, on the device, an app can’t access the user’s contacts without their permission. From the documentation:

On iOS 6.0 and later, if the caller does not have access to the Address Book database:

• For apps linked against iOS 6.0 and later, this function returns NULL.

• For apps linked against previous version of iOS, this function returns an empty read-only database.

If you haven’t seen the permissions alert come up (“SomeApp would like access to your contacts”), it’s possible that the direct address-book APIs just assume that they don’t have access and silently fail; you might have to display something from the AddressBookUI framework to trigger it.

Toffic answered 20/9, 2012 at 17:12 Comment(1)
Yep, you're right. Need to use ABAddressBookRequestAccessWithCompletion(). How can I check if this method exists though (for < iOS 6)? I asked the followup question here: #12518268Dryly
D
52

I created a helper class, AddressBookHelper, to handle backward compatibility. Here are the guts:

-(BOOL)isABAddressBookCreateWithOptionsAvailable {
    return &ABAddressBookCreateWithOptions != NULL;
}

-(void)loadContacts {
    ABAddressBookRef addressBook;
    if ([self isABAddressBookCreateWithOptionsAvailable]) {
        CFErrorRef error = nil;
        addressBook = ABAddressBookCreateWithOptions(NULL,&error);
        ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
            // callback can occur in background, address book must be accessed on thread it was created on
            dispatch_async(dispatch_get_main_queue(), ^{
                if (error) {
                    [self.delegate addressBookHelperError:self];
                } else if (!granted) {
                    [self.delegate addressBookHelperDeniedAcess:self];
                } else {
                    // access granted
                    AddressBookUpdated(addressBook, nil, self);
                    CFRelease(addressBook);
                }
            });
        });
    } else {
        // iOS 4/5
        addressBook = ABAddressBookCreate();
        AddressBookUpdated(addressBook, NULL, self);
        CFRelease(addressBook);
    }
}

void AddressBookUpdated(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
    AddressBookHelper *helper = (AddressBookHelper *)context;
    ABAddressBookRevert(addressBook);
    CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);

    // process the contacts to return
    NSArray *contacts = ...    

    [[helper delegate] addressBookHelper:helper finishedLoading:contacts];
};
Dur answered 21/9, 2012 at 15:52 Comment(8)
Updated: added some missing CFRelease calls to prevent leaking AddressBook in this code.Dur
Hi, just wondering why the call to dispatch_async()? I've tried without it and it works perfectly, and also doesn't block my UI while the address book is reloading.Liven
The documentation states that The completion handler is called on an arbitrary queue. If your app uses an address book throughout the app, you are responsible for ensuring that all usage of that address book is dispatched to a single queue to ensure correct thread-safe operation. In testing, I had some crashes (can't recall the details) that suggested thread safety issues, which led me to the docs.Dur
I suppose it may be more correct to get the current queue using dispatch_get_current_queue, and reference that queue in the callback block. In my case, this code is always invoked on the main thread, so that wasn't necessary. I haven't noticed any UI blocking using this code in 2 apps--the UI updates immediately. But YMMV, depending what you're doing in your callback method.Dur
Ah right yep that makes sense. Do you mind putting that in your answer? I'm not sure if many people will read through the comments to discover this :)Liven
The documentation for dispatch_get_current_queue says The dispatch_get_current_queue() function is only recommended for debugging and logging purposes. Code must not make any assumptions about the queue returned, unless it is one of the global queues or a queue the code has itself created., so I'm inclined to leave it as is, assuming it's invoked on the main queue. I suppose the whole body of loadContacts could be dispatched to a locally created queue.Dur
Why the call to ABAddressBookRevert? (BTW thanks much for this)Layton
It probably isn't relevant for most use cases, but it's based on the documentation for ABExternalChangeCallback: "The addressBook object does not take any action to flush or synchronize cached state with the Address Book database. If you want to ensure that addressBook doesn’t contain stale values, use ABAddressBookRevert."Dur
T
12

Probably related to the new privacy controls—as of iOS 6, on the device, an app can’t access the user’s contacts without their permission. From the documentation:

On iOS 6.0 and later, if the caller does not have access to the Address Book database:

• For apps linked against iOS 6.0 and later, this function returns NULL.

• For apps linked against previous version of iOS, this function returns an empty read-only database.

If you haven’t seen the permissions alert come up (“SomeApp would like access to your contacts”), it’s possible that the direct address-book APIs just assume that they don’t have access and silently fail; you might have to display something from the AddressBookUI framework to trigger it.

Toffic answered 20/9, 2012 at 17:12 Comment(1)
Yep, you're right. Need to use ABAddressBookRequestAccessWithCompletion(). How can I check if this method exists though (for < iOS 6)? I asked the followup question here: #12518268Dryly
M
12

Try with this: access to the address book must be granted before it can be access programmatically. Here is what I ended up doing.

  #import <AddressBookUI/AddressBookUI.h>

  // Request authorization to Address Book
  ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, NULL);

  if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
    ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
      // First time access has been granted, add the contact
      [self _addContactToAddressBook];
    });
  }
  else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
    // The user has previously given access, add the contact
    [self _addContactToAddressBook];
  }
  else {
    // The user has previously denied access
    // Send an alert telling user to change privacy setting in settings app
  }
Malacology answered 10/1, 2013 at 11:52 Comment(0)
J
0

Probably related to the new privacy controls, as of iOS 6, on the device, an app can’t access the user’s contacts without their permission.

Code:

-(void)addressBookValidation
{



NSUserDefaults *prefs=[NSUserDefaults standardUserDefaults];
ABAddressBookRef addressbook = ABAddressBookCreate();
__block BOOL accessGranted = NO;

if (ABAddressBookRequestAccessWithCompletion != NULL)
{
    if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
    {
        dispatch_semaphore_t sema = dispatch_semaphore_create(0);
        ABAddressBookRequestAccessWithCompletion(addressbook, ^(bool granted, CFErrorRef error)
                                                 {
                                                     accessGranted = granted;
                                                     dispatch_semaphore_signal(sema);
                                                 });
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        dispatch_release(sema);
    }
    else if(ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized)
    {
        accessGranted = YES;
    }
    else if (ABAddressBookGetAuthorizationStatus()==kABAuthorizationStatusDenied)
    {
        accessGranted = NO;
    }
    else if (ABAddressBookGetAuthorizationStatus()==kABAuthorizationStatusRestricted){
        accessGranted = NO;
    }
    else
    {
        accessGranted = YES;
    }


}
else
{
    accessGranted = YES;
}
[prefs setBool:accessGranted forKey:@"addressBook"];

NSLog(@"[prefs boolForKey:@'addressBook']--->%d",[prefs boolForKey:@"addressBook"]);
[prefs synchronize];
CFRelease(addressbook);
}
Jourdain answered 28/6, 2013 at 12:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.