33 votes

Gestion des contacts dupliqués en raison de cartes liées dans l'API Carnet d'adresses d'iOS

Certains bêta-utilisateurs de ma prochaine application signalent que la liste des contacts contient beaucoup d'enregistrements en double. J'utilise le résultat de ABAddressBookCopyArrayOfAllPeople comme source de données pour mon tableau personnalisé de contacts, et je ne comprends pas pourquoi les résultats sont différents de ceux de l'application "Contacts" de l'iPhone.

En regardant de plus près l'application Contacts, il semble que les doublons proviennent d'entrées avec des "cartes liées". Les captures d'écran ci-dessous ont été un peu obscurcies, mais comme vous pouvez le voir, dans mon application, à l'extrême droite, "Céline" apparaît deux fois, alors que dans l'application Contacts, à gauche, il n'y a qu'un seul "Céline". Si vous cliquez sur la ligne de ce seul contact, vous obtenez une carte "Infos unifiées" avec deux "Cartes liées". (comme indiqué au centre, je n'ai pas utilisé les coordonnées de Céline car elles ne tenaient pas sur une seule capture d'écran) :

Screenshot

Les questions relatives aux "cartes liées" ont tout à fait a quelques sujets sur Les forums d'Apple pour les utilisateurs finaux, mais outre le fait que beaucoup pointent vers une Page d'assistance 404 Je ne peux pas, de manière réaliste, réparer les carnets d'adresses de tous les utilisateurs de mon application. Je préférerais traiter ce problème de manière élégante et sans déranger l'utilisateur. Pour aggraver les choses, il semble que je ne sois pas le seul à avoir ce problème, puisque WhatsApp affiche la même liste contenant des contacts en double. .

Pour être clair sur l'origine des contacts en double, je ne stocke pas, ne mets pas en cache ou n'essaie pas d'être intelligent avec le tableau. ABAddressBookCopyArrayOfAllPeople retours. Les enregistrements en double proviennent donc directement de l'appel API.

Quelqu'un sait-il comment gérer ou détecter ces cartes liées, en empêchant les enregistrements en double d'apparaître ? L'application Contacts d'Apple le fait, comment pouvons-nous le faire nous aussi ?

MISE À JOUR : J'ai écrit une bibliothèque et l'ai mise sur Cocoapods pour résoudre le problème en question. Voir ma réponse ci-dessous

30voto

Daniel Amitay Points 5578

Une méthode consisterait à récupérer les contacts uniquement à partir de la source du carnet d'adresses par défaut :

ABAddressBookRef addressBook = ABAddressBookCreate();
NSArray *people = (__bridge NSArray *)ABAddressBookCopyArrayOfAllPeopleInSource(addressBook, ABAddressBookCopyDefaultSource(addressBook));

Mais c'est nul, non ? Il cible le carnet d'adresses de l'appareil, mais pas les contacts supplémentaires qui pourraient se trouver dans Exchange ou d'autres carnets d'adresses de synchronisation fantaisistes.

Voici donc la solution que vous recherchez :

  1. Itération dans les références ABRecord
  2. Saisir les "références liées" respectives (à l'aide de ABPersonCopyArrayOfAllLinkedPeople )
  3. Les regrouper dans un NSSet (afin que le regroupement puisse être identifié de manière unique).
  4. Ajouter ce NSSet à un autre NSSet
  5. Le profit ?

Vous avez maintenant un NSSet contenant des NSSets d'objets ABRecord liés. Le NSSet global aura le même nombre que le nombre de contacts dans votre application "Contacts".

Exemple de code :

NSMutableSet *unifiedRecordsSet = [NSMutableSet set];

ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef records = ABAddressBookCopyArrayOfAllPeople(addressBook);
for (CFIndex i = 0; i < CFArrayGetCount(records); i++)
{
    NSMutableSet *contactSet = [NSMutableSet set];

    ABRecordRef record = CFArrayGetValueAtIndex(records, i);
    [contactSet addObject:(__bridge id)record];

    NSArray *linkedRecordsArray = (__bridge NSArray *)ABPersonCopyArrayOfAllLinkedPeople(record);
    [contactSet addObjectsFromArray:linkedRecordsArray];

    // Your own custom "unified record" class (or just an NSSet!)
    DAUnifiedRecord *unifiedRecord = [[DAUnifiedRecord alloc] initWithRecords:contactSet];

    [unifiedRecordsSet addObject:unifiedRecord];
    CFRelease(record);
}

CFRelease(records);
CFRelease(addressBook);

_unifiedRecords = [unifiedRecordsSet allObjects];

6voto

J'utilise ABPersonCopyArrayOfAllLinkedPeople() dans mon application depuis un certain temps déjà. Malheureusement, je viens de découvrir qu'elle ne fait pas toujours ce qu'il faut. Par exemple, si vous avez deux contacts qui ont le même nom mais que l'un d'eux a l'indicateur "isPerson" activé et l'autre non, la fonction ci-dessus ne les considérera pas comme "liés". Pourquoi cela pose-t-il un problème ? Parce que les sources Gmail(exchange) ne prennent pas en charge cet indicateur booléen. Si vous essayez de l'enregistrer comme faux, cela échouera, et le contact que vous avez enregistré dans cette source reviendra lors de la prochaine exécution de votre application comme non lié au contact que vous avez enregistré dans iCload (CardDAV).

Situation similaire avec les services sociaux : Gmail ne les supporte pas et la fonction ci-dessus verra deux contacts avec les mêmes noms comme différents si l'un a un compte facebook et l'autre non.

Je suis en train de passer à mon propre algorithme, basé sur le nom et l'ID de l'enregistrement source uniquement, pour déterminer si deux enregistrements de contact doivent être affichés comme un seul contact. C'est plus de travail mais il y a une lueur d'espoir : ABPersonCopyArrayOfAllLinkedPeople() est très lent.

5voto

epologee Points 3812

L'approche que @Daniel Amitay a fourni contenait des pépites de grande valeur, mais malheureusement le code n'est pas prêt à être utilisé. Avoir une bonne recherche sur les contacts est crucial pour mon application et pour beaucoup d'autres, donc j'ai passé pas mal de temps pour y parvenir, tout en abordant également la question de l'accès au carnet d'adresses compatible avec iOS 5 et 6 (en gérant l'accès utilisateur via des blocs). Cela résout à la fois les nombreuses cartes liées dues à des sources de synchronisation incorrectes et les cartes provenant de la nouvelle intégration de Facebook.

La bibliothèque que j'ai écrite utilise un magasin Core Data en mémoire (éventuellement sur disque) pour mettre en cache les ID des enregistrements du carnet d'adresses, fournissant un algorithme de recherche facile à exécuter en arrière-plan qui renvoie des cartes de carnet d'adresses unifiées.

La source est disponible sur un Dépôt github de la mienne qui est un CocoaPods pod :

pod 'EEEUnifiedAddressBook'

5voto

Raiden996 Points 126

Avec le nouvel iOS 9 Cadre des contacts vous pouvez enfin avoir vos contacts unifiés.

Je vous montre deux exemples :

1) Utilisation de l'énumération rapide

//Initializing the contact store:
CNContactStore* contactStore = [CNContactStore new];
if (!contactStore) {
    NSLog(@"Contact store is nil. Maybe you don't have the permission?");
    return;
}

//Which contact keys (properties) do you want? I want them all!
NSArray* contactKeys = @[ 
    CNContactNamePrefixKey, CNContactGivenNameKey, CNContactMiddleNameKey, CNContactFamilyNameKey, CNContactPreviousFamilyNameKey, CNContactNameSuffixKey, CNContactNicknameKey, CNContactPhoneticGivenNameKey, CNContactPhoneticMiddleNameKey, CNContactPhoneticFamilyNameKey, CNContactOrganizationNameKey, CNContactDepartmentNameKey, CNContactJobTitleKey, CNContactBirthdayKey, CNContactNonGregorianBirthdayKey, CNContactNoteKey, CNContactImageDataKey, CNContactThumbnailImageDataKey, CNContactImageDataAvailableKey, CNContactTypeKey, CNContactPhoneNumbersKey, CNContactEmailAddressesKey, CNContactPostalAddressesKey, CNContactDatesKey, CNContactUrlAddressesKey, CNContactRelationsKey, CNContactSocialProfilesKey, CNContactInstantMessageAddressesKey
];

CNContactFetchRequest* fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:contactKeys];
[fetchRequest setUnifyResults:YES]; //It seems that YES is the default value
NSError* error = nil;
__block NSInteger counter = 0;

Et ici, je boucle sur tous les contacts unifiés en utilisant l'énumération rapide :

BOOL success = [contactStore enumerateContactsWithFetchRequest:fetchRequest
                                                         error:&error
                                                    usingBlock:^(CNContact* __nonnull contact, BOOL* __nonnull stop) {
                                                        NSLog(@"Unified contact: %@", contact);
                                                        counter++;
                                                    }];
if (success) {
    NSLog(@"Successfully fetched %ld contacts", counter);
}
else {
    NSLog(@"Error while fetching contacts: %@", error);
}

2) Utilisation unifiedContactsMatchingPredicate API :

// Contacts store initialized ...
NSArray * unifiedContacts = [contactStore unifiedContactsMatchingPredicate:nil keysToFetch:contactKeys error:&error]; // Replace the predicate with your filter.

P.S Vous serez peut-être aussi intéressé par cette nouvelle API de CNContact.h :

/*! Returns YES if the receiver was fetched as a unified contact and includes the contact having contactIdentifier in its unification */
- (BOOL)isUnifiedWithContactWithIdentifier:(NSString*)contactIdentifier;

0voto

Hafthor Points 5663

Je récupère toutes les sources (ABAddressBookCopyArrayOfAllSources), je déplace la source par défaut (ABAddressBookCopyDefaultSource) à la première position, puis j'itère à travers elles et je récupère toutes les personnes de la source (ABAddressBookCopyArrayOfAllPeopleInSource) en sautant celles que j'ai vues liées auparavant, puis je récupère les personnes liées sur chacune (ABPersonCopyArrayOfAllLinkedPeople).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X