Sunday, October 14, 2018

Can't add unified CNContact to CNGroup in iOS

Leave a Comment

Here's what I'm doing:

- (void)doCreateGroup {     [[self contentView] endEditing:true];      NSString * newString = [[[[self contentView] groupNameField] text] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];      NSString * firstError = nil;     if ([newString length] == 0) {         firstError = @"Missing group name";     }      NSError * groupsError = nil;     NSArray * groups = [self.contactStore groupsMatchingPredicate:nil error:&groupsError];      for (CNGroup * group in groups) {         if ([group.name isEqualToString:newString]) {             firstError = @"Group already exists";         }     }      if (firstError) {         [self presentViewController:[WLGCommonUtilities doProcessErrorWithOkay:@"Error" errorMessage:firstError] animated:YES completion:nil];         return;     }      CNMutableGroup * newGroup = [CNMutableGroup new];     [newGroup setName:newString];      CNSaveRequest *saveRequest = [CNSaveRequest new];     [saveRequest addGroup:newGroup toContainerWithIdentifier:nil];      NSError * error = nil;     [self.contactStore executeSaveRequest:saveRequest error:&error];     if (error) {         [self presentViewController:[WLGCommonUtilities doProcessErrorWithOkay:@"Error" errorMessage:[error localizedDescription]] animated:YES completion:nil];     } else {         CNSaveRequest *saveRequest2 = [CNSaveRequest new];         NSArray * groupsAgain = [self.contactStore groupsMatchingPredicate:nil error:&groupsError];         CNGroup * gotGroup;         for (CNGroup * group in groupsAgain) {             if ([group.name isEqualToString:newString]) {                 gotGroup = group;             }         }         for (CNContact * contact in self.selectedContactsArray) {             [saveRequest2 addMember:contact toGroup:gotGroup];         }          NSError * error1 = nil;         [self.contactStore executeSaveRequest:saveRequest2 error:&error1];         if (error) {             [self presentViewController:[WLGCommonUtilities doProcessErrorWithOkay:@"Error" errorMessage:[error1 localizedDescription]] animated:YES completion:nil];         } else {             [[self navigationController] dismissViewControllerAnimated:true completion:nil];         }     } } 

this works to create a CNGroup and then add contacts to said CNGroup. Works for all contacts EXCEPT for unified contacts. I've tried everything possible to make this work and it just doesn't. It likely has something to do with the unified CNContact's identifier since that identifier is only stored in temp memory so it can't be added to a CNGroup since it doesn't really haver a REAL CNContact identifier. Contacts framework is a mess! Any help would be appreciated. I've also filed a tech support request with Apple.

EDIT: One way to get around this is to use Address Framework that is now deprecated. I can add as many unified contacts to Address groups by doing this.

ABRecordRef group = ABGroupCreate(); ABAddressBookAddRecord(addressBook, group, nil);  ABRecordSetValue(group, kABGroupNameProperty,@"My Groups", nil); for (int i=0;i < nPeople;i++) {     ABRecordRef ref = CFArrayGetValueAtIndex(allPeople,i);     ABGroupAddMember(group, ref, nil);     ABAddressBookSave(addressBook, nil); } 

this does save everything in the contact book to a group, all visible contacts that is. so it does store the Unified contact into the group. if you unlink the contacts while they are in a group, both contacts stay within the group. so the old framework works to solve this. just seems ridiculous that it can't be solved with new Contacts framework. Again, I may be missing something with the new Contacts framework, so if this is possible with the current Contacts framework in iOS please let me know.

2 Answers

Answers 1

i figured it out. this is a mess

step one:

NSMutableArray * finalArray = [NSMutableArray array]; NSMutableArray * unifiedContacts = [NSMutableArray array]; NSMutableArray * fullContacts = [NSMutableArray array];  CNContactFetchRequest * request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys]; [request setSortOrder:CNContactSortOrderGivenName]; [self.contactStore enumerateContactsWithFetchRequest:request error:&error usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {     [unifiedContacts addObject:contact]; }];  CNContactFetchRequest * request2 = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys]; [request2 setUnifyResults:false]; [request2 setSortOrder:CNContactSortOrderGivenName]; [self.contactStore enumerateContactsWithFetchRequest:request2 error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {     [fullContacts addObject:contact]; }];  for (CNContact * contctUn in unifiedContacts) {     NSMutableArray * nestedContacts = [NSMutableArray array];     for (CNContact * contct in fullContacts) {         if ([contctUn isUnifiedWithContactWithIdentifier:contct.identifier]) {             [nestedContacts addObject:contct];         }     }     if (nestedContacts.count) {         [finalArray addObject:@{@"contact" : contctUn, @"linked" : nestedContacts}];     } else {         [finalArray addObject:@{@"contact" : contctUn}];     } }  self.mainArray = [finalArray mutableCopy]; 

this pulls in all contacts from unified contacts and then pulls in all un-unified contacts, splices the groups together and saves them as dictionaries with "linked" being an array of linked contacts if the contact is indeed linked to the contact in question.

step 2: create a group ... this is pretty simple, no need to show the code since this is pretty easy

step 3:

for (id obj in self.filteredSearchArray) {     if ([obj valueForKey:@"linked"]) {         for (id obj2 in [obj valueForKey:@"linked"]) {             [self.selectedContactsArray addObject:obj2];         }     } }  CNSaveRequest *saveRequest2 = [CNSaveRequest new]; for (CNContact * contact in self.selectedContactsArray) {     [saveRequest2 addMember:contact toGroup:[newGroup copy]]; }  NSError * error1 = nil; [self.contactStore executeSaveRequest:saveRequest2 error:&error1]; 

self.selectedContactsArray is the array that contains the contacts you want in the group. it contains all contacts you want in the group in addition it contains the sublinked contacts if a contact you want in the group is linked to a user.

when this save request executes the group now contains the unified contact.

this is a mess. Contacts Framework in iOS is a mess, but this works. No app that creates groups for contacts has solve this, so here's the million dollar solution.

Answers 2

That seems odd indeed. As at least a workaround, have you tried to fetch the selected contacts with a CNContactFetchRequest that has its unifyResults set to false?

I mean, I don't know where you get the selectedContactsArray from, I assume either you can modify an existing request that gave you that data accordingly or you have to somehow refetch the contacts again. That's probably really, really ugly, as you would have to construct a fetch request with a predicate or key set that is guaranteed to match the same contacts (and only those contacts) plus said unifyResults member set to false.

I'd imagine something like this (sorry for using swift, it's a little compacter for me right now, I hope that's okay):

let allMyIds: [String] = self.selectedContactsArray.map { $0.identifier } let predicate: NSPredicate = CNContact.predicateForContacts(withIdentifiers: allMyIds)  let fetchRequest = CNContactFetchRequest(keysToFetch: someKeys)  // not sure what you'd need here for someKeys...  // I assume it would have to be a key definitely present in all contacts you  // are interested in, e.g. name? I might be wrong though...  fetchRequest.unifyResults = false _ = self.contactStore.enumerateContacts(with: fetchRequest, usingBlock: { contact, errorPointer in      // your group adding/save preparation code here }) 

I admit I am not that familiar with the Contacts framework, so I can't say whether that is really feasible. Especially the set of keys you'd have to provide to the enumerate... method might be tricky if you don't have a key that's guaranteed to be part of all contacts you want.

I apologize for such a half-baked answer, but maybe it can at least give you a new impulse.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment