2012-07-05 12 views
38

Algunos usuarios beta de my upcoming app informan que la lista de contactos contiene una gran cantidad de 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 a los de la aplicación 'Contactos' del iPhone.Tratar con contactos duplicados debido a tarjetas vinculadas en la API de la libreta de direcciones de iOS

Al observar más de cerca la aplicación Contactos, parece que los duplicados se originan en 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 "Información unificada" con dos "Tarjetas vinculadas" (como se muestra en el centro, no usé los datos de contacto de Celine porque no cabían en una captura de pantalla):

Screenshot

las cuestiones en torno a "Tarjetas Vinculado" tienen un quitefew topics en Apple's forums para los usuarios finales, pero aparte del hecho de que muchos apuntan a un 404 support page, no puedo ir por ahí de forma realista la fijación de todos 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 is showing the same list containing duplicate contacts.

Solo para aclarar los orígenes de los contactos duplicados, no estoy almacenando, almacenando en caché o tratando de ser inteligente sobre la matriz 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 en cuestión. Ver mi respuesta a continuación

+1

Creo que este es un problema mayor en iOS 6, con los contactos de Facebook. Pero desde que estoy de vuelta en iOS5 no puedo verificar si es igual ... – Jankeesvw

+0

Facebook probablemente también agrega tarjetas vinculadas ... Entonces alguien debe haber pensado en la forma correcta de mostrar una lista, si no es ABAddressBookCopyArrayOfAllPeople – epologee

Respuesta

5

El enfoque que @ Daniel Amitay proporcionado contenía pepitas de gran valor, pero por desgracia, 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 fondo fácil que devuelve tarjetas de libreta de direcciones unificadas.

La fuente está disponible en una github repository of mine, que es una vaina CocoaPods:

pod 'EEEUnifiedAddressBook' 
+0

404 en el repositorio, ¿lo movió? – andreacipriani

+0

Ah, sí, desde que se movió a un repositorio separado. Se necesita un poco de actualización, me temo. – epologee

33

Un método sería la de recuperar sólo los contactos de la libreta de direcciones de fuente por defecto:

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á buscando:

  1. Iterar a través de la ABRecord hace referencia a
  2. Grab cada respectivos "referencias vinculadas" (usando ABPersonCopyArrayOfAllLinkedPeople)
  3. ellos Bundle en un NSSet (para que la agrupación se pueda identificar de forma exclusiva)
  4. Agregue ese NSSet a otro NSSet
  5. ¿Beneficio?

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 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]; 
+0

Es poco convincente Apple no nos proporciona esto, ¡pero su libreta de direcciones unificada resuelve el problema perfectamente! Dejaré la pregunta abierta en caso de que alguien más cercano a Apple quiera opinar, pero creo que los +100 pronto serán tuyos :) ¡Gracias! – epologee

+0

Daniel, me pregunto por qué eliminó el repositorio DAUnifiedAddressBook de GitHub. Suena como algo útil. –

+0

=/Fue una medida provisional que no había desarrollado completamente. En realidad, simplemente tenía una llamada "generateUnifiedContacts" que realizaba los pasos anteriores. Tenía planes para desarrollarlo aún más, pero al final no habría sido mucho mejor que hacer lo anterior usted mismo y luego manejar las referencias como lo desee. –

8

He estado usando ABPersonCopyArrayOfAllLinkedPeople() en mi aplicación desde hace algún 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 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 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.

+1

Bienvenido a Stack Overflow. Hay dos respuestas anteriores, una que ya se votó bastante, y otra del cartel original que identifica lo que realmente hicieron para resolver el problema. No estoy seguro de si lo que estás proponiendo es significativamente diferente de lo que ha colocado para que otros lo utilicen en una descarga de Github, pero no estoy seguro de que tu respuesta sea realmente útil, ya que no está claro si otros pueden acceder a tu código de solución. No voy a votar por usted, pero tenga en cuenta si la respuesta que proporciona es proporcionar nueva información cuando se trata de una pregunta anterior como esta. –

+3

Creo que la respuesta de Christopher fue principalmente para proporcionar información de contexto sobre las posibles lagunas en la implementación de ABPersonCopyArrayOfAllLinkedPeople(). No he visto esa información en otra parte, por lo que creo que podría ser útil para mí o para otros. –

+0

Creo que esta es una respuesta altamente informativa, e incluye información que de otro modo sería difícil de obtener que sea pertinente a la pregunta y respuestas dadas previamente. Upvoted. – Tim

0

Me estoy poniendo todas las fuentes ABAddressBookCopyArrayOfAllSources, moviendo el que viene por defecto ABAddressBookCopyDefaultSource a la primera posición, a continuación, iterar a través de ellos y conseguir todas las personas de origen ABAddressBookCopyArrayOfAllPeopleInSource saltarse los que he visto ligada antes, a continuación, obtener las personas vinculadas a cada ABPersonCopyArrayOfAllLinkedPeople.

4

Con el nuevo iOS 9 Contacts Framework finalmente puede tener sus contactos unificados.

te muestran dos ejemplos:

1) El uso de la 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í bucle a través de todos los contactos unificados mediante la 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) Uso de unifiedContactsMatchingPredicate API :

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

P.S Usted puede que 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; 
Cuestiones relacionadas