web-dev-qa-db-ja.com

iOSのアドレス帳APIでリンクされたカードが原因の連絡先の重複への対処

私の今後のアプリ のベータユーザーの一部は、連絡先のリストに重複したレコードが多数含まれていると報告しています。 ABAddressBookCopyArrayOfAllPeople の結果を連絡先のカスタマイズされたテーブルビューのデータソースとして使用していますが、結果がiPhoneの「連絡先」アプリとは異なることに困惑しています。

連絡先アプリをさらに詳しく見ると、重複は「リンクされたカード」のエントリから発生しているようです。以下のスクリーンショットは少し難読化されていますが、私の右端のアプリに表示されているように、「Celine」が2回表示されますが、左側の連絡先アプリには「Celine」が1つしかありません。その単一の連絡先の行をクリックすると、2つの「リンクされたカード」が表示された「Unified Info」カードが表示されます(中央に示されているように、私はCelineの連絡先の詳細を使用しませんでした。 1つのスクリーンショットに合わせる)

Screenshot

「リンクカード」に関する問題には、エンドユーザー向けの Appleのフォーラムかなり a いくつかのトピック がありますが、多くの点が 404サポートページ 、実際にアプリのユーザーのアドレス帳をすべて修正することはできません。私はむしろ、ユーザーを煩わすことなく、エレガントに対処したいと思っています。さらに悪いことに、この問題の原因は WhatsAppが重複する連絡先を含む同じリストを表示している なので、私だけではないようです。

重複した連絡先の起源について明確にするために、配列ABAddressBookCopyArrayOfAllPeopleが返す値を格納したり、キャッシュしたり、スマートにしようとしたりはしていません。したがって、重複したレコードはAPI呼び出しから直接取得されます。

これらのリンクされたカードを処理または検出し、重複したレコードが表示されないようにする方法を知っている人はいますか? Appleの連絡先アプリはそれを行いますが、残りの人もそれをどのように行うことができますか?

更新:ライブラリを作成し、Cocoapodsに配置して問題を解決しました。以下の私の答えを見てください

38
epologee

@Daniel Amitayが提供したアプローチには大きな価値のあるナゲットが含まれていましたが、残念ながらコードは使用する準備ができていません。私と多くのアプリにとって連絡先を適切に検索することは非常に重要であるため、これを正しく理解するためにかなりの時間を費やし、一方でiOS 5および6互換のアドレス帳アクセスの問題(ブロックを介したユーザーアクセスの処理)にも取り組みました)。ソースが正しく同期されていないためにリンクされた多くのカードと、新しく追加されたFacebook統合からのカードの両方を解決します。

私が書いたライブラリは、メモリ内(オプションでディスク上)のコアデータストアを使用してアドレス帳のレコードIDをキャッシュし、統合されたアドレス帳カードを返す簡単なバックグラウンドスレッド検索アルゴリズムを提供します。

ソースは 私のgithubリポジトリ で利用できます CocoaPods ポッドです:

pod 'EEEUnifiedAddressBook'
5
epologee

1つの方法は、デフォルトのアドレス帳ソースからのみ連絡先を取得することです。

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

しかし、それは不十分ですよね?これはデバイス上のアドレス帳をターゲットにしますが、Exchangeまたはその他の高度な同期アドレス帳にある可能性のある追加の連絡先はターゲットにしません。

これがあなたが探している解決策です:

  1. ABRecord参照を反復処理します
  2. それぞれの「リンクされた参照」を取得(ABPersonCopyArrayOfAllLinkedPeopleを使用)
  3. それらをNSSetにバンドルします(グループ化を一意に識別できるようにするため)。
  4. そのNSSetを別のNSSetに追加します
  5. 利益?

これで、リンクされたABRecordオブジェクトのNSSetsを含む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];
35
Daniel Amitay

私はしばらくの間、アプリでABPersonCopyArrayOfAllLinkedPeople()を使用しています。残念ながら、私はそれが常に正しいことをするわけではないことを発見しました。たとえば、同じ名前の連絡先が2つあるが、一方には「isPerson」フラグが設定されていて、もう一方には設定されていない場合、上記の関数はそれらを「リンク済み」と見なしません。なぜこれが問題なのですか? Gmail(交換)ソースはこのブールフラグをサポートしていないためです。 falseとして保存しようとすると失敗し、保存した連絡先は、iCload(CardDAV)に保存した連絡先からリンク解除された状態で、次回のアプリの実行時に返されます。

ソーシャルサービスでの同様の状況:Gmailはそれらをサポートしておらず、1つのFacebookアカウントを持っている場合と持っていない場合、上記の関数では同じ名前の2つの連絡先が異なるものとして表示されます。

2つの連絡先レコードを1つの連絡先として表示する必要があるかどうかを判断するために、独自の名前とソースレコードIDのみのアルゴリズムに切り替えています。より多くの作業が必要ですが、銀の裏打ちがあります。ABPersonCopyArrayOfAllLinkedPeople()はお尻が遅いです。

8

新しいiOS 9 Contacts Frameworkを使用すると、最終的に統合された連絡先を使用できます。

2つの例を示します。

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 AP​​Iの使用:

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

すべてのソースをABAddressBookCopyArrayOfAllSources取得し、デフォルトのABAddressBookCopyDefaultSourceを最初の位置に移動してから、それらを反復処理し、ソースからすべての人々を取得しますABAddressBookCopyArrayOfAllPeopleInSource以前にリンクされたものをスキップして、各ABPersonCopyArrayOfAllLinkedPeopleでリンクされた人々を取得します。

0
Hafthor