iosobjective-ccncontactcncontactstore

How do I get only the first phone number from every contact on a device?


I have a table view that displays the names of all contacts on the device. These names are coming from an array called contactsArray. For each contact object I'm getting the phoneNumbers object and pulling the numbers into another array called phoneNumberArray. When I put them into my table view it displays each contact with it's corresponding number...but only for so long. When I get down a few dozen rows the numbers aren't matched up with the correct contact anymore because some contact objects contained a phoneNumbers object with multiple phone numbers. How can I get only the first phone number of each object so that I have an equal number of contacts and phone numbers?

Here is my code:

    @property (nonatomic, strong) NSMutableArray *contactsArray;
    @property (nonatomic, strong) NSMutableArray *phoneNumberArray;

    @end

    @implementation Contacts

    - (void)viewDidLoad
    {

        [super viewDidLoad];

        self.phoneNumberArray = [[NSMutableArray alloc]init];
        self.contactsArray = [[NSMutableArray alloc]init];
        [self fetchContactsandAuthorization];
    }

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
        return 1;
    }

    -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        return  self.contactsArray.count;
    }

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

        CNContact *contact = self.contactsArray[indexPath.row];
        NSString *phone = self.phoneNumberArray[indexPath.row];

        NSString *contactName = [NSString stringWithFormat:@"%@ %@",contact.givenName,contact.familyName];
        NSString *contactNumber = phone;

        cell.name.text = contactName;
        cell.number.text = contactNumber;
        [cell.inviteBtn addTarget:self action:@selector(openMessagesForContact:) forControlEvents:UIControlEventTouchUpInside];

        return cell;
    }

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return 72;
    }

    -(void)fetchContactsandAuthorization
    {
        // Request authorization to Contacts
        CNContactStore *store = [[CNContactStore alloc] init];
        [store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (granted == YES)
            {
                CNContactStore *addressBook = [[CNContactStore alloc]init];

                NSArray *keysToFetch =@[CNContactFamilyNameKey,
                                        CNContactGivenNameKey,
                                        CNContactPhoneNumbersKey];

                CNContactFetchRequest *fetchRequest = [[CNContactFetchRequest alloc]initWithKeysToFetch:keysToFetch];

                [addressBook enumerateContactsWithFetchRequest:fetchRequest error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {

                    [self.contactsArray addObject:contact];

                    NSString *phone;

                    for (CNLabeledValue *label in contact.phoneNumbers) {
                        phone = [label.value stringValue];
                        if ([phone length] > 0) {
                            [self.phoneNumberArray addObject:phone];
                        }
                    }
                }];

                dispatch_async(dispatch_get_main_queue(), ^{
                    [self.contactsTableView reloadData];
                });

            }
        }];
    }

@end

Solution

  • The problem with your approach is that you are creating a global array of phone numbers and then adding ALL of the phone numbers to this. Instead you only want to add the FIRST phone number.

    I would modify the following code:

    for (CNLabeledValue *label in contact.phoneNumbers) {
        phone = [label.value stringValue];
        if ([phone length] > 0) {
            [self.phoneNumberArray addObject:phone];
            break;
        }
    }
    
    if (contact.phoneNumbers.count == 0) {
        [self.phoneNumberArray addObject: @""];
    }
    

    The logic has now changed:

    1. We add each user to an array
    2. In the phone number array we only add their corresponding first number
    3. Now our two arrays are directly linked. It is worth checking at the end that the phone number array always has the same count as the user array
    4. We call break to exit

    With this new functionality - and touched on in 3. We need to add some more checking. For example what if a user doesn't have any phone numbers - in this case we need to add a blank phone number to ensure the numbers stay appropriate to the contacts.

    if ([label isEqualTo: contact.phoneNumbers.lastValue]) {
        [self.phoneNumberArray addObject: @""];
    }
    

    Also above is another potential check - if we loop through all the phone numbers and don't add a number then we add a blank number anyway. You need to look at your data and see the different scenarios you might encounter.

    Alternative solution:

    The above solution is a quick fix to get your code working - personally though I am not a big fan of that approach - what if you want to use more than one number for each? What if you want to click a user to view all numbers? When architecting code it is worth thinking about ways to ensure that it can be refactored more easily later.

    Not going to different (Coredata and objects etc) I would prefer to have an array of dictionaries for each contact. This array would store their details (name, phone number etc) and would be easily accessible in each cell.

    [addressBook enumerateContactsWithFetchRequest:fetchRequest error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
    
        NSMutableArray * phoneNumbersTemp = [NSMutableArray new];
    
        for (CNLabeledValue *label in contact.phoneNumbers) {
            phone = [label.value stringValue];
            if ([phone length] > 0) {
                [phoneNumbersTemp addObject:phone];
            }
        }
    
        NSDictionary * contactDict = @{name: contact.name,
                                       phoneNumbers: phoneNumbersTemp}
    
        [self.contactsArray addObject:contactDict];
    }];
    

    Note: This is sudo code and has not been tested - it is to highlight the method

    This kind of code will then allow you to access the first phone number in each cell (just get the contact phone dictionary for each user and get the first entry) but also allow you to build a contact object for advanced functionality.