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
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:
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.