objective-c - los - eliminar contactos duplicados gmail
Tratar con contactos duplicados debido a las tarjetas vinculadas en la API de la libreta de direcciones de iOS (5)
Algunos usuarios beta de mi próxima aplicación informan que la lista de contactos contiene muchos registros duplicados. Estoy usando el resultado de ABAddressBookCopyArrayOfAllPeople
como la fuente de datos para mi vista de tabla personalizada de contactos, y me desconcierta que los resultados sean diferentes de la aplicación ''Contactos'' del iPhone.
Al observar más de cerca la aplicación Contactos, parece que los duplicados se originan en las entradas con "Tarjetas vinculadas". Las capturas de pantalla siguientes se han ofuscado un poco, pero como puede ver en mi aplicación en el extremo derecho, "Celine" aparece dos veces, mientras que en la aplicación Contactos a la izquierda solo hay una "Celine". Si hace clic en la fila de ese único contacto, obtiene una tarjeta de "Información unificada" con dos "Tarjetas vinculadas" (como se muestra en el centro, no usé los datos de contacto de Celine porque no encajaban en una captura de pantalla) :
Los problemas relacionados con las "Tarjetas vinculadas" tienen quite temas en los foros de Apple para los usuarios finales, pero aparte del hecho de que muchos apuntan a una página de soporte 404 , no puedo solucionar de manera realista todas las libretas de direcciones de los usuarios de mi aplicación. Me gustaría mucho tratar con elegancia y sin molestar al usuario. Para empeorar las cosas, parece que no soy el único con este problema, ya que WhatsApp muestra la misma lista que contiene contactos duplicados .
Solo para aclarar los orígenes de los contactos duplicados, no estoy almacenando, almacenando en caché ni tratando de ser inteligente sobre la matriz que devuelve ABAddressBookCopyArrayOfAllPeople
. Entonces, los registros duplicados provienen directamente de la llamada API.
¿Alguien sabe cómo tratar o detectar estas tarjetas vinculadas, evitando que aparezcan registros duplicados? La aplicación de Contactos de Apple lo hace, ¿cómo podemos hacer el resto de nosotros también?
ACTUALIZACIÓN: Escribí una biblioteca y la puse en Cocoapods para resolver el problema. Ver mi respuesta a continuación
Con el nuevo iOS 9 Contacts Framework , finalmente puede tener sus contactos unificados.
Te muestro dos ejemplos:
1) Usando enumeración rápida
//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;
Y aquí recorro todos los contactos unificados usando enumeración rápida:
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) Utilizando la API unifiedContactsMatchingPredicate
:
// Contacts store initialized ...
NSArray * unifiedContacts = [contactStore unifiedContactsMatchingPredicate:nil keysToFetch:contactKeys error:&error]; // Replace the predicate with your filter.
PD. Quizás también esté interesado en esta nueva 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;
El enfoque que proporcionó @Daniel Amitay contenía nuggets de gran valor, pero desafortunadamente el código no está listo para su uso. Tener una buena búsqueda en los contactos es crucial para mi y muchas aplicaciones, así que dediqué bastante tiempo a hacer esto bien, mientras que también abordo el tema del acceso a la libreta de direcciones compatible con iOS 5 y 6 (manejo de acceso de usuarios a través de bloques ) Resuelve tanto las muchas tarjetas vinculadas debido a las fuentes sincronizadas incorrectamente como las tarjetas de la integración de Facebook recién agregada.
La biblioteca que escribí utiliza un almacén de datos centrales en memoria (opcionalmente en el disco) para almacenar en caché los ID del registro de la libreta de direcciones, proporcionando un algoritmo de búsqueda de subprocesos fácil de fondo que devuelve tarjetas de libreta de direcciones unificadas.
La fuente está disponible en un repositorio mío de github , que es un pod de CocoaPods :
pod ''EEEUnifiedAddressBook''
He estado usando ABPersonCopyArrayOfAllLinkedPeople () en mi aplicación desde hace un tiempo. Desafortunadamente, acabo de descubrir que no siempre hace lo correcto. Por ejemplo, si tiene dos contactos que tienen el mismo nombre pero uno tiene el indicador "isPerson" establecido y el otro no, la función anterior no los considerará "vinculados". ¿Por qué es esto un problema? Porque las fuentes de Gmail (intercambio) no admiten este indicador booleano. Si intenta guardarlo como falso, fallará, y el contacto que guardó en él volverá a aparecer en la siguiente ejecución de su aplicación como desvinculado del contacto que guardó en iCload (CardDAV).
Situación similar con los servicios sociales: Gmail no los admite y la función anterior verá dos contactos con los mismos nombres que diferentes si uno tiene una cuenta de Facebook y otra no.
Estoy cambiando a mi propio algoritmo de name-and-source-recordID-only para determinar si dos registros de contacto deben mostrarse como un único contacto. Más trabajo, pero hay un lado positivo: ABPersonCopyArrayOfAllLinkedPeople () es lento.
Un método sería recuperar solo los contactos de la fuente predeterminada de la libreta de direcciones:
ABAddressBookRef addressBook = ABAddressBookCreate();
NSArray *people = (__bridge NSArray *)ABAddressBookCopyArrayOfAllPeopleInSource(addressBook, ABAddressBookCopyDefaultSource(addressBook));
Pero eso es cojo, ¿verdad? Se dirige a la libreta de direcciones en el dispositivo, pero no a los contactos adicionales que pueden estar en Exchange u otras elegantes libretas de direcciones de sincronización.
Así que aquí está la solución que estás buscando:
- Itera a través de las referencias ABRecord
- Tome cada "referencia vinculada" respectiva (usando
ABPersonCopyArrayOfAllLinkedPeople
) - Agrúpelos en un NSSet (para que la agrupación pueda identificarse de manera única)
- Agregar ese NSSet a otro NSSet
- ¿Lucro?
Ahora tiene un NSSet que contiene NSSets de objetos ABRecord vinculados. El NSSet general tendrá el mismo conteo que la cantidad de contactos en su aplicación "Contactos".
Código de ejemplo:
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];
ABAddressBookCopyArrayOfAllSources
todas las fuentes ABAddressBookCopyArrayOfAllSources
, moviendo el predeterminado ABAddressBookCopyDefaultSource
a la primera posición, luego ABAddressBookCopyDefaultSource
y ABAddressBookCopyDefaultSource
que todas las personas de la fuente ABAddressBookCopyArrayOfAllPeopleInSource
las que he visto vinculadas antes, y luego se vincula gente en cada ABPersonCopyArrayOfAllLinkedPeople
.