1

I want to use ABPeoplePickerNavigationController but I want to customise the view. I want to have an accessory image by some of the contacts, and I want to sort them in a different way than the default one. Is there a way to do it? Or do I have to create my own UITableViewController?

bobsacameno
  • 765
  • 3
  • 10
  • 25
  • 1
    You have to create your own `UITableViewController` – Mohit Aug 14 '14 at 13:24
  • `You should not need to subclass these controllers; the expected way to modify their behavior is by your implementation of their delegate` this is what apple document says. https://developer.apple.com/library/ios/documentation/ContactData/Conceptual/AddressBookProgrammingGuideforiPhone/Chapters/UI_Controllers.html#//apple_ref/doc/uid/TP40007744-CH5-SW1 – Mohit Aug 14 '14 at 13:33
  • Agreed: You'll have to create your own table view if you want to customize the appearance of the contacts in this manner. By the way, if you're looking for ways to get the contacts so that you can subsequently sort them and show them in your own table view, you can see http://stackoverflow.com/a/23418263/1271826. – Rob Aug 14 '14 at 13:58
  • Thanks. When building my own UITableViewController, is there a way I can get the array of the contacts from ABAddressBook or do I have to copy them to my own array? If so, how should I copy them? – bobsacameno Aug 14 '14 at 14:08
  • @Rob Thanks. I have this code for getting an array of the contacts: `ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, nil); self.allContacts = (__bridge NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBookRef);` This works for me, but when I display this array in the `UITableViewController` It looks not sorted. Do you know why? I thought it would return an array sorted by the alphabet of the contact names. – bobsacameno Aug 14 '14 at 16:19
  • First, use `__bridge_transfer` (or `CFBridgingRelease`), not `__bridge`, or else your app will leak. Second, you can sort if you use `ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering`. Or just sort the array yourself using one of the myriad sorting routines, such as `sortedArrayUsingComparator`. – Rob Aug 14 '14 at 17:46

1 Answers1

1

You'll have to create your own table view if you want to customize the appearance of the contacts in this manner. For example, you can extract the contacts using:

- (void)loadContacts
{
    ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();

    if (status == kABAuthorizationStatusDenied) {
        // if you got here, user had previously denied/revoked permission for your
        // app to access the contacts, and all you can do is handle this gracefully,
        // perhaps telling the user that they have to go to settings to grant access
        // to contacts

        [[[UIAlertView alloc] initWithTitle:nil message:@"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
        return;
    }

    CFErrorRef error = NULL;
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);

    if (error) {
        NSLog(@"ABAddressBookCreateWithOptions error: %@", CFBridgingRelease(error));
        if (addressBook) CFRelease(addressBook);
        return;
    }

    ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
        if (error) {
            NSLog(@"ABAddressBookRequestAccessWithCompletion error: %@", CFBridgingRelease(error));
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            if (granted) {
                // if they gave you permission, then get copy of contacts and reload table

                self.allContacts = CFBridgingRelease(ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering(addressBook, NULL, kABPersonSortByLastName));

                [self.tableView reloadData];
            } else {
                // however, if they didn't give you permission, handle it gracefully, for example...

                [[[UIAlertView alloc] initWithTitle:nil message:@"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
            }

            if (addressBook) CFRelease(addressBook);
        });
    });
}

And you can then use this array in your cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    ABRecordRef person = (__bridge ABRecordRef)self.allContacts[indexPath.row];

    NSMutableArray *nameArray = [NSMutableArray array];

    NSString *prefix    = CFBridgingRelease(ABRecordCopyValue(person, kABPersonPrefixProperty));
    if (prefix) [nameArray addObject:prefix];

    NSString *firstName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));
    if (firstName) [nameArray addObject:firstName];

    NSString *middleName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonMiddleNameProperty));
    if (middleName) [nameArray addObject:middleName];

    NSString *lastName  = CFBridgingRelease(ABRecordCopyValue(person, kABPersonLastNameProperty));
    if (lastName) [nameArray addObject:lastName];

    NSString *fullname = [nameArray componentsJoinedByString:@" "];

    NSString *suffix    = CFBridgingRelease(ABRecordCopyValue(person, kABPersonSuffixProperty));
    if (suffix) {
        fullname = [NSString stringWithFormat:@"%@, %@", fullname, suffix];
    }

    cell.textLabel.text = fullname;

    NSString *company   = CFBridgingRelease(ABRecordCopyValue(person, kABPersonOrganizationProperty));
    if ([fullname length] == 0) {
        cell.textLabel.text = company;
        cell.detailTextLabel.text = nil;
    } else {
        cell.detailTextLabel.text = company;
    }

    if ([nameArray count] == 0 && [company length] == 0)
        NSLog(@"nothing to show!!!");

    return cell;
}

Obviously, given the entire idea was that you wanted to customize the cell, modify the cellForRowAtIndexPath accordingly, but hopefully this illustrates the idea.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • When using this code I get that I see some of my contacts twice. I noticed that some of these contacts have FaceTime. How can I set that each contact appears only once? – bobsacameno Aug 17 '14 at 10:15
  • I just figured out that the contacts the appearing twice on my table is a result of being contact on the iPhone, and on the iCloud. Is there a way of getting these to appear only once? – bobsacameno Aug 17 '14 at 23:00
  • A couple of options: 1. You could enumerate the "sources" and then let the user pick a source and then get all of the contacts for that one source. 2. You could manually find and prune duplicates (but when you find a duplicate, I'm not sure how you'd determine which one "wins" if there are minor differences). 3. You could merge your iCloud contacts with your iPhone contacts, so you only have the one source. – Rob Aug 18 '14 at 02:35
  • 1. How do I specify a which source to use, and what's the proper name for the source `iPhone` and the source `iCloud`? 2. Manually means that I need to go over each contact and to compare it to all of the other contacts, right? There's no easier way to do so. 3. merging the contacts means that the user has to do that on the iPhone itself. Or merging is something that the app can do only for the `self.allContacts` you defined in the answer? (and so it'll be something like solution 2). – bobsacameno Aug 18 '14 at 07:49
  • 1. You would use something like `ABAddressBookCopyArrayOfAllSources` to get the array of sources. 2. Yes, manually means iterating through the contacts yourself, though if they're sorted, you can probably just compare it to the prior record rather than comparing them against all other contacts. 3. Yes, I meant that if you (or any user) accidentally had your contacts duplicated in two different sources, you'd go through the process on your iPhone yourself. In terms of iPhone livability, having unnecessary dupes in different sources is a real hassle (easy to forget which one you're working on). – Rob Aug 18 '14 at 12:49