Подтвердить что ты не робот

Работа с дублирующимися контактами из-за связанных карт в API адресной книги iOS

Некоторые бета-пользователи мое предстоящее приложение сообщают, что список контактов содержит много дубликатов записей. Я использую результат ABAddressBookCopyArrayOfAllPeople в качестве источника данных для моего настроенного вида таблицы контактов, и это меня озадачивает, что результаты разные из приложения iPhone "Контакты" .

При более пристальном рассмотрении приложения "Контакты" кажется, что дубликаты берутся из записей с "Связанными картами". Скриншоты ниже немного запутались, но, как вы видите в моем приложении справа, "Celine" появляется дважды, в то время как в приложении "Контакты" слева есть только одна "Celine". Если вы нажмете на строку этого единственного контакта, вы получите карту "Унифицированная информация" с двумя "Связанными картами" (как показано в центре, я не использовал контактные данные Celine, потому что они не подходили на один снимок экрана):

Screenshot

Проблемы вокруг "Связанных карт" довольно a несколько тем on форумы Apple для конечных пользователей, но кроме того, что многие указывают на страница поддержки 404, я не могу реалистично обойти исправления всех адресных книг моих пользователей приложений. Я бы очень хотел иметь дело с ним элегантно и не беспокоить пользователя. Хуже того, кажется, что я не единственная проблема с этой проблемой, поскольку WhatsApp показывает тот же список, содержащий дубликаты контактов.

Для того, чтобы быть ясным о происхождении дублированных контактов, я не храню, не кэширую и не пытаюсь быть умным в отношении массива ABAddressBookCopyArrayOfAllPeople. Таким образом, дубликаты записей поступают непосредственно из вызова API.

Кто-нибудь знает, как бороться с этими связанными картами или обнаруживать их, предотвращая появление дубликатов записей? Приложение Apple Contacts делает это, как остальные могут сделать это тоже?

ОБНОВЛЕНИЕ: я написал библиотеку и поместил ее на Cocoapods, чтобы решить эту проблему. См. Мой ответ ниже

4b9b3361

Ответ 1

Подход, который предоставил @Daniel Amitay, содержал самородки большой ценности, но, к сожалению, код не готов к использованию. Хороший поиск по контактам имеет решающее значение для моего и многих приложений, поэтому я потратил немало времени на это, в то время как на стороне также рассматривалась проблема доступа к адресной книге iOS 5 и 6 (обработка доступа пользователей через блоки). Он решает как многие связанные карты из-за неправильно синхронизированных источников, так и карт из недавно добавленной интеграции Facebook.

В библиотеке, которую я написал, используется хранилище Core Data в памяти (опционально на диске) для кэширования идентификаторов записи в адресной книге, что обеспечивает простой алгоритм поиска в фоновом режиме, который возвращает унифицированные карты адресной книги.

Источник доступен в репозитории github, который является CocoaPods pod:

pod 'EEEUnifiedAddressBook'

Ответ 2

Один из методов заключается только в том, чтобы извлекать контакты из источника адресной книги по умолчанию:

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

Но это хромает, верно? Он предназначен для адресной книги на устройстве, но не для дополнительных контактов, которые могут быть в Exchange или других необычных синхронизирующих адресных книгах.

Итак, вот решение, которое вы ищете:

  • Итерации через ссылки ABRecord
  • Возьмите все соответствующие "связанные ссылки" (используя ABPersonCopyArrayOfAllLinkedPeople)
  • Свяжите их в NSSet (чтобы можно было однозначно идентифицировать группировку)
  • Добавьте, что NSSet к другому NSSet
  • Profit?

Теперь у вас есть NSSet, содержащий NSSets связанных объектов ABRecord. Всеобъемлющий NSSet будет иметь тот же счет, что и количество контактов в вашем приложении "Контакты".

Пример кода:

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

Ответ 3

Я использую ABPersonCopyArrayOfAllLinkedPeople() в своем приложении уже некоторое время. К сожалению, я только что обнаружил, что он не всегда поступает правильно. Например, если у вас есть два контакта с тем же именем, но у одного есть флаг "isPerson", а другой нет, указанная выше функция не будет считать их "связанными". Почему это проблема? Поскольку источники Gmail (обмен) не поддерживают этот логический флаг. Если вы попытаетесь сохранить его как ложное, он будет терпеть неудачу, и контакт, который вы сохранили в нем, будет возвращен при следующем запуске вашего приложения как несвязанный с контактом, который вы сохранили в iCload (CardDAV).

Аналогичная ситуация с социальными службами: Gmail не поддерживает их, и вышеприведенная функция увидит два контакта с теми же именами, что и разные, если у вас есть учетная запись в facebook, а другая нет.

Я перехожу к своему собственному алгоритму с именем-и-source-recordID-only для определения того, должны ли две записи контактов отображаться как один контакт. Больше работы, но есть серебряная подкладка: ABPersonCopyArrayOfAllLinkedPeople() - медленный.

Ответ 4

В новой iOS 9 Структура контактов вы можете, наконец, иметь свои объединенные контакты.

Я покажу вам два примера:

1) Использование быстрого перечисления

//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;

И здесь я просматриваю все унифицированные контакты, используя быстрое перечисление:

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) Используя unifiedContactsMatchingPredicate API:

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

P.S Возможно, вам также интересен этот новый API 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;

Ответ 5

Я получаю все источники ABAddressBookCopyArrayOfAllSources, перемещая по умолчанию ABAddressBookCopyDefaultSource в первую позицию, затем перебираю их и получаю всех людей из исходных ABAddressBookCopyArrayOfAllPeopleInSource пропущенных, которые я видел ранее, а затем получаю ссылки людей на каждом ABPersonCopyArrayOfAllLinkedPeople.