APAddressBook icon indicating copy to clipboard operation
APAddressBook copied to clipboard

Filter LinkedContacts

Open pabloivan57 opened this issue 10 years ago • 11 comments

We're dealing with this problem http://stackoverflow.com/questions/11351454/dealing-with-duplicate-contacts-due-to-linked-cards-in-ios-address-book-api. Looking at the docs seems there's no way to filter those linked contacts using APAddressBook, is this true? Maybe I missed something?

pabloivan57 avatar Aug 14 '14 23:08 pabloivan57

Hello I've heard about this problem, APAddressBook even have a pull request that should solve this issue. But I was unable to reproduce this bug and didn't merge it. Please, provide steps how to create linked contacts that cause duplicates and I'll try to find solution

belkevich avatar Aug 15 '14 15:08 belkevich

I'm having the same problem and believe I have steps that you can use to reproduce: 0. Start simulator and Reset Content & Settings to be sure you're in a fresh state.

  1. Open Contacts app and create two contacts: First Name: First Last Name: User Phone: 314-555-1212 and First Name: Second Last Name: User Phone: 314-555-1212
  2. Open up First User contact and tap Edit
  3. Scroll to bottom of edit view and tap "link contacts..."
  4. Select Second User and tap "Link"
  5. Tap "Done"

At this point, you can no longer see both "First User" and "Second User" in the list of All Contacts. However, they both get iterated over in the -[APAddressBook loadContactsOnQueue:completion:] completion block.

I suspect the right behavior here is to not to exclude any linked APContact objects (as that pull request does, I believe), but maybe to return a new field on APContact that has recordIDs of linked contacts (or strong relationships to the linked APContact objects). Unfortunately, it does not appear that Apple's ABAddressBookCopyArrayOfAllPeople() method returns the unified contact, so if you stop returning linked contacts beyond the first, there could be other non-shared fields that are missing.

goddardcm avatar Nov 07 '14 22:11 goddardcm

Sorry for dealy. I'll try to reproduce the steps and think about best solution

belkevich avatar Nov 12 '14 20:11 belkevich

I am also getting duplicate contacts when using APAddressBook: 2015-01-28 21 40 28

(The screenshots are from the built-in Contacts app, the black stuff is me logging out APContact fields from contacts I got back when using your library)

Is there any way to get the underlying record so I can inspect the links myself and sort out what is linked and what is not?

blixt avatar Jan 29 '15 02:01 blixt

Does annyone have a soulution yet ?

helgetan avatar Mar 20 '15 15:03 helgetan

I think it can be filtered with ABPersonCopyArrayOfAllLinkedPeople. I'll try to make a PR for this

emptyway avatar May 29 '15 03:05 emptyway

seems that its already implemented on #39 and was released just a fews hours ago.. :)

emptyway avatar May 29 '15 03:05 emptyway

With the latest release. I managed to filter unique contacts with this:

    APAddressBook *addressBook = [[APAddressBook alloc] init];
    addressBook.fieldsMask = APContactFieldDefault | APContactFieldRecordID | APContactFieldLinkedRecordIDs;

    NSMutableArray *uniqueContact = [NSMutableArray new];
    addressBook.filterBlock = ^BOOL(APContact *contact) {
        BOOL shouldInclude = [contact.phones count] > 0 && ![uniqueContact containsObject:contact.recordID];
        if (shouldInclude) {
            [uniqueContact addObject:contact.recordID];
            [uniqueContact addObjectsFromArray:contact.linkedRecordIDs];
        }
        return shouldInclude;
    };

    [addressBook loadContacts:^(NSArray *contacts, NSError *error) {
       // unique contacts with phone number
    }];

emptyway avatar Jun 10 '15 05:06 emptyway

@simonlinj I've checked you solution. It works, but have an issue with contact list order. You can reproduce it.

  1. Create contact named 'XXX' (for example, it has recordID 250)
  2. Create contact name 'YYY' (it has greater recordID because it created later)
  3. Link 'XXX' into 'YYY' not otherwise. So you'll see only 'YYY' record in Contacts.app
  4. Run your code and you'll see 'XXX', not 'YYY'. It's happened because ABAddressBookCopyArrayOfAllPeople returns contacts sorted by RecordID.

So, I have no idea how Contacts.app works)) If you (or someone else) are still interesting in this feature, please share your thoughts how to implement this feature.

belkevich avatar Oct 06 '15 16:10 belkevich

Hi, everybody Now I'm testing on iOS 9 and make some investigation. For testing I have created in simulator 3 additional contact, 2 of them was linked to 3th. Other contacts, exclude one, was deleted. So what I have found:

  1. all linked contacts stored separately (i.e. has different record with different recordID),
  2. there is no any merged contact in address book storage.
  3. every contact has linkedIDs propery, which gives info that they are merged. But I don't understand, how Contacts.app gives name for unified contact. Anybody can clarify that?

I wrote code for unifying linked contacts after retriving from store by APAddressBook library:

private func filterLinkedContacts(contacts: [AddressBookContact]) -> [AddressBookContact] {
        var res = [AddressBookContact]()
        var linkedDic: [Set<NSNumber> : [AddressBookContact]] = Dictionary()
        for contact in contacts {
            if contact.linkedRecordIDs?.count > 0  {
                var linkedContactIDs = contact.linkedRecordIDs!
                linkedContactIDs.append(contact.recordID)
                let linkS = Set(linkedContactIDs)
                if var linkedContacts = linkedDic[linkS] {
                    linkedContacts.append(contact)
                    linkedDic[linkS] = linkedContacts
                } else {
                    linkedDic[linkS] = [contact]
                }
            } else {
                res.append(contact)
            }
        }
        for (_, linkedContacts) in linkedDic {
            let unified = unifyLinkedContacts(linkedContacts)
            res.append(unified)
        }

        return res
    }

    private func unifyLinkedContacts(linkedContacts: [AddressBookContact]) -> AddressBookContact {
        let unified = linkedContacts.first!
        var unifiedNums = Set<String>()
        var unifiedEmails = Set<String>()
        for subLink in linkedContacts {
            if let nums = subLink.phones  {
                for n in nums {
                    unifiedNums.insert(n)
                }
            }
            if let emails = subLink.emails {
                for e in emails {
                    unifiedEmails.insert(e)
                }
            }
        }
        unified.emails = Array(unifiedEmails)
        unified.phones = Array(unifiedNums)
        return unified
    }

Where AddressBookContact is wrapper for APContact (with public properties setters). So it's obvious that unified contact will be named by first entry and it can differ from Contacts.app!

ArniDexian avatar Oct 13 '15 07:10 ArniDexian

@ArniDexian looks like Contacts.app use some private API. If anybody find the solution let me know

belkevich avatar Oct 13 '15 08:10 belkevich