web-dev-qa-db-fra.com

Comment utiliser correctement la méthode ABAddressBookCreateWithOptions dans iOS 6?

J'essaie de comprendre les méthodes ABAdressBookCreateWithOptions et ABAddressBookRequestAccessWithCompletion dans iOS 6.

La plupart des informations que j'ai pu trouver sont les suivantes: "Pour demander l'accès aux données de contact, appelez la fonction ABAddressBookRequestAccessWithCompletion après avoir appelé la fonction ABAddressBookCreateWithOptions".

Je crois qu'ensemble, ces méthodes devraient alerter l'utilisateur pour décider d'autoriser ou non l'accès de l'application aux contacts, mais lorsque je les utilise, je ne vois aucune invite.

Quelqu'un pourrait-il fournir un exemple de code expliquant comment ces méthodes devraient être appelées ensemble dans un exemple réel? Comment créer des options (CFDictionary)? J'ai du code de travail utilisant la méthode ABAddressBookCreate obsolète, mais je dois mettre à jour vers iOS 6 pour répondre aux problèmes de confidentialité.

Merci d'avance à tous ceux qui peuvent nous éclairer ici!

34
codeqi

Maintenant que le NDA a été levé, voici ma solution pour cela où vous devez remplacer une méthode qui retourne un tableau. (Si vous préférez ne pas bloquer pendant que l'utilisateur décide et êtes prêt à réécrire potentiellement une partie de votre code existant, veuillez consulter la solution de David ci-dessous):

ABAddressBookRef addressBook = ABAddressBookCreate();

__block BOOL accessGranted = NO;

if (ABAddressBookRequestAccessWithCompletion != NULL) { // we're on iOS 6
    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 { // we're on iOS 5 or older
    accessGranted = YES;
}


if (accessGranted) {

    NSArray *thePeople = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
    // Do whatever you need with thePeople...

}

J'espère que cela aide quelqu'un ...

83
Engin Kurutepe

La plupart des réponses que j'ai vues à cette question font des choses compliquées et folles avec GCD et finissent par bloquer le thread principal. Ce n'est pas nécessaire!

Voici la solution que j'utilise (fonctionne sur iOS 5 et iOS 6):

- (void)fetchContacts:(void (^)(NSArray *contacts))success failure:(void (^)(NSError *error))failure {
  if (ABAddressBookRequestAccessWithCompletion) {
    // on iOS 6

    CFErrorRef err;
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &err);

    if (err) {
      // handle error
      CFRelease(err);
      return;
    }

    ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
      // ABAddressBook doesn't gaurantee execution of this block on main thread, but we want our callbacks to be
      dispatch_async(dispatch_get_main_queue(), ^{
        if (!granted) {
          failure((__bridge NSError *)error);
        } else {
          readAddressBookContacts(addressBook, success);
        }
        CFRelease(addressBook);
      });
    });
  } else {
    // on iOS < 6

    ABAddressBookRef addressBook = ABAddressBookCreate();
    readAddressBookContacts(addressBook, success);
    CFRelease(addressBook);
  }
}

static void readAddressBookContacts(ABAddressBookRef addressBook, void (^completion)(NSArray *contacts)) {
  // do stuff with addressBook
  NSArray *contacts = @[];

  completion(contacts);
}
23
Nik

L'autre réponse de haut rang a des problèmes:

  • il appelle inconditionnellement des API qui n'existent pas dans iOS antérieures à 6, de sorte que votre programme se bloquera sur les anciens appareils.
  • il bloque le thread principal, de sorte que votre application ne répond pas et ne progresse pas pendant la durée de l'alerte système.

Voici mon point de vue MRC à ce sujet:

        ABAddressBookRef ab = NULL;
        // ABAddressBookCreateWithOptions is iOS 6 and up.
        if (&ABAddressBookCreateWithOptions) {
          NSError *error = nil;
          ab = ABAddressBookCreateWithOptions(NULL, (CFErrorRef *)&error);
    #if DEBUG
          if (error) { NSLog(@"%@", error); }
    #endif
          if (error) { CFRelease((CFErrorRef *) error); error = nil; }
        }
        if (ab == NULL) {
          ab = ABAddressBookCreate();
        }
        if (ab) {
          // ABAddressBookRequestAccessWithCompletion is iOS 6 and up.
          if (&ABAddressBookRequestAccessWithCompletion) {
            ABAddressBookRequestAccessWithCompletion(ab,
               ^(bool granted, CFErrorRef error) {
                 if (granted) {
                   // constructInThread: will CFRelease ab.
                   [NSThread detachNewThreadSelector:@selector(constructInThread:)
                                            toTarget:self
                                          withObject:ab];
                 } else {
                   CFRelease(ab);
                   // Ignore the error
                 }
                 // CFErrorRef should be owned by caller, so don't Release it.
               });
          } else {
            // constructInThread: will CFRelease ab.
            [NSThread detachNewThreadSelector:@selector(constructInThread:)
                                     toTarget:self
                                   withObject:ab];
          }
        }
      }
22
DavidPhillipOster

Ceci est lié de manière périphérique à la question d'origine, mais je ne l'ai vu mentionnée nulle part ailleurs, et il m'a fallu environ deux jours pour le comprendre. Si vous enregistrez un rappel pour les modifications du carnet d'adresses, il DOIT être sur le thread principal.

Par exemple, dans ce code, seul sync_address_book_two () sera appelé:

ABAddressBookRequestAccessWithCompletion(_addressBook, ^(bool granted, CFErrorRef error) {
    if (granted) {
        ABAddressBookRegisterExternalChangeCallback (_addressBook, sync_address_book_one, NULL);
        dispatch_async(dispatch_get_main_queue(), ^{
            ABAddressBookRegisterExternalChangeCallback (_addressBook, sync_address_book_two, NULL);
        });
    }
});
2
Eli Burke