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!
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 ...
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);
}
L'autre réponse de haut rang a des problèmes:
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];
}
}
}
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);
});
}
});