38

我即将推出的应用程序的一些 beta 用户报告说联系人列表包含大量重复记录。我使用来自的结果ABAddressBookCopyArrayOfAllPeople作为我的自定义联系人表格视图的数据源,令我感到困惑的是,结果与 iPhone 的“联系人”应用程序不同。

当更仔细地查看联系人应用程序时,似乎重复来自带有“链接卡”的条目。下面的屏幕截图有些模糊,但正如您在最右侧的应用程序中看到的那样,“Celine”出现了两次,而在左侧的“联系人”应用程序中只有一个“Celine”。如果您单击该单个联系人的行,您会得到一张“统一信息”卡片,其中包含两个“链接卡片” (如中间所示,我没有使用 Celine 的联系方式,因为它们不适合一张截图)

截屏

围绕“Linked Cards”的问题在Apple 的最终用户论坛上有很多主题,但除了许多指向404 支持页面这一事实之外,我实际上无法修复所有应用程序用户的地址簿。我更愿意优雅地处理它而不打扰用户。更糟糕的是,似乎我不是唯一遇到此问题的人,因为WhatsApp 显示的是包含重复联系人的相同列表

只是为了清楚重复联系人的来源,我没有存储、缓存或以其他方式尝试对数组ABAddressBookCopyArrayOfAllPeople返回进行智能处理。因此,重复记录直接来自 API 调用。

有谁知道如何处理或检测这些链接卡,防止出现重复记录?Apple 的通讯录应用程序做到了,我们其他人怎么能做到呢?

更新:我编写了一个库并将其放在 Cocoapods 上以解决手头的问题。请看下面我的回答

4

5 回答 5

35

一种方法是仅从默认通讯簿源中检索联系人:

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

但这很蹩脚,对吧?它针对的是设备上的通讯录,但不是 Exchange 或其他花哨的同步通讯录中的额外联系人。

所以这是您正在寻找的解决方案:

  1. 遍历 ABRecord 引用
  2. 抓住每个相应的“链接参考”(使用ABPersonCopyArrayOfAllLinkedPeople
  3. 将它们捆绑在一个 NSSet 中(以便可以唯一标识分组)
  4. 将该 NSSet 添加到另一个 NSSet
  5. 利润?

您现在有一个 NSSet,其中包含链接的 ABRecord 对象的 NSSet。总体 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];
于 2012-07-14T01:43:47.640 回答
8

我在我的应用程序中使用 ABPersonCopyArrayOfAllLinkedPeople() 已经有一段时间了。不幸的是,我刚刚发现它并不总是做正确的事情。例如,如果您有两个同名的联系人,但一个设置了“isPerson”标志而另一个没有设置,则上述函数不会将它们视为“链接”。为什么这是一个问题?因为 Gmail(exchange) 来源不支持此布尔标志。如果您尝试将其保存为 false,它将失败,并且您保存在其中的联系人将在您的应用程序的下一次运行时返回,因为与您在 iCload (CardDAV) 中保存的联系人取消链接。

社交服务的类似情况:Gmail 不支持它们,如果一个有 facebook 帐户而一个没有,上述功能将看到两个同名的联系人不同。

我正在切换到我自己的 name-and-source-recordID-only 算法来确定是否应将两个联系人记录显示为单个联系人。更多的工作,但有一线希望:ABPersonCopyArrayOfAllLinkedPeople() 很慢。

于 2012-10-07T04:28:46.940 回答
5

@Daniel Amitay 提供的方法包含了非常有价值的块,但不幸的是代码还没有准备好使用。对联系人进行良好的搜索对我和许多应用程序至关重要,因此我花了很多时间来解决这个问题,同时还解决了与 iOS 5 和 6 兼容的地址簿访问问题(通过块处理用户访问)。它解决了由于不正确同步源导致的许多链接卡片和来自新添加的 Facebook 集成的卡片。

我编写的库使用内存(可选磁盘)Core Data 存储来缓存地址簿记录 ID,提供简单的后台线程搜索算法,返回统一的地址簿卡片。

源代码在我的 github 存储库中可用,这是一个CocoaPods pod:

pod 'EEEUnifiedAddressBook'
于 2012-10-03T20:10:45.953 回答
5

借助新的 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)使用unifiedContactsMatchingPredicateAPI​​:

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

PS 你可能也对这个新的 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;
于 2015-09-21T14:35:07.317 回答
0

我正在获取所有源ABAddressBookCopyArrayOfAllSources,将默认源移到ABAddressBookCopyDefaultSource第一个位置,然后遍历它们并让所有的人从源中ABAddressBookCopyArrayOfAllPeopleInSource跳过我以前见过的链接,然后在每个ABPersonCopyArrayOfAllLinkedPeople.

于 2013-02-15T00:26:38.733 回答