[background: iOS 7, Xcode 5, updated to February 2014. Test data is an address book entry with multiple phone numbers and multiple addresses in addition to the basic contact information on the iPhone 5 (real device, not a simulator)]
My goal is to use the AddressBookUI methods to allow the user to specify a contact, and then use various fields (addresses, phone numbers, etc.) to populate the GUI in my code. ABPeoplePickerNavigationController is a standard mechanism that allows the user to select a contact by name. This leads to a call to this delegate method:
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
However, if I study a person’s record at this point, none of the fields with multiple values has any data. Therefore, I extracted the RecordID extracted, and as a result, ABRecordRef also did not fill in multivalued fields.
If I return YES from the delegate method, the user will be shown a different user interface with the contact details displayed. Touching any field causes a delegate method to be called.
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
and that ABRecordRef has all the filled value fields.
I can’t find any information about lazy loading of records or there will be a need for a delay or resolution that would prevent filling out the fields. And the code that I use to check the records is a method, so the same code that finds the values in the second instance cannot find it in the first.
Any suggestions on what might happen, or how can I get full records without displaying a second user interface to the user?
I am using Apple QuickContacts sample code. Here are the additions and changes I made.
+(NSMutableDictionary *)convertABRecordRef:(ABRecordRef)person { // Initialize a mutable dictionary and give it initial values. NSMutableDictionary *contactInfoDict = [[NSMutableDictionary alloc] initWithCapacity:12]; // Use a general Core Foundation object. CFTypeRef generalCFObject = ABRecordCopyValue(person, kABPersonFirstNameProperty); ABRecordID foundId = ABRecordGetRecordID(person); NSNumber *personIDNum = [NSNumber numberWithInteger:foundId]; [contactInfoDict setObject:personIDNum forKey:@"recordID"]; // Get the first name. if (generalCFObject) { [contactInfoDict setObject:(__bridge NSString *)generalCFObject forKey:@"firstName"]; CFRelease(generalCFObject); } // Get the last name. generalCFObject = ABRecordCopyValue(person, kABPersonLastNameProperty); if (generalCFObject) { [contactInfoDict setObject:(__bridge NSString *)generalCFObject forKey:@"lastName"]; CFRelease(generalCFObject); } generalCFObject = ABRecordCopyValue(person, kABPersonOrganizationProperty); if (generalCFObject) { [contactInfoDict setObject:(__bridge NSString *)generalCFObject forKey:@"companyName"]; CFRelease(generalCFObject); } //ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty); //NSArray *numbers = (NSArray *)ABMultiValueCopyArrayOfAllValues(phones); // Get the phone numbers as a multi-value property. ABMultiValueRef phonesRef = ABRecordCopyValue(person, kABPersonPhoneProperty); CFIndex phoneCount = ABMultiValueGetCount(phonesRef); for (CFIndex i=0; i<ABMultiValueGetCount(phonesRef); i++) { CFStringRef currentPhoneLabel = ABMultiValueCopyLabelAtIndex(phonesRef, i); CFStringRef currentPhoneValue = ABMultiValueCopyValueAtIndex(phonesRef, i); if (CFStringCompare(currentPhoneLabel, kABPersonPhoneMobileLabel, 0) == kCFCompareEqualTo) { [contactInfoDict setObject:(__bridge NSString *)currentPhoneValue forKey:@"mobileNumber"]; } if (CFStringCompare(currentPhoneLabel, kABHomeLabel, 0) == kCFCompareEqualTo) { [contactInfoDict setObject:(__bridge NSString *)currentPhoneValue forKey:@"homeNumber"]; } if (CFStringCompare(currentPhoneLabel, kABWorkLabel, 0) == kCFCompareEqualTo) { [contactInfoDict setObject:(__bridge NSString *)currentPhoneValue forKey:@"workNumber"]; } CFRelease(currentPhoneLabel); CFRelease(currentPhoneValue); } CFRelease(phonesRef); // Get the e-mail addresses as a multi-value property. ABMultiValueRef emailsRef = ABRecordCopyValue(person, kABPersonEmailProperty); for (int i=0; i<ABMultiValueGetCount(emailsRef); i++) { CFStringRef currentEmailLabel = ABMultiValueCopyLabelAtIndex(emailsRef, i); CFStringRef currentEmailValue = ABMultiValueCopyValueAtIndex(emailsRef, i); if (CFStringCompare(currentEmailLabel, kABHomeLabel, 0) == kCFCompareEqualTo) { [contactInfoDict setObject:(__bridge NSString *)currentEmailValue forKey:@"homeEmail"]; } if (CFStringCompare(currentEmailLabel, kABWorkLabel, 0) == kCFCompareEqualTo) { [contactInfoDict setObject:(__bridge NSString *)currentEmailValue forKey:@"workEmail"]; } CFRelease(currentEmailLabel); CFRelease(currentEmailValue); } CFRelease(emailsRef); // Get the first street address among all addresses of the selected contact. ABMultiValueRef addressRef = ABRecordCopyValue(person, kABPersonAddressProperty); if (ABMultiValueGetCount(addressRef) > 0) { CFIndex numberOfAddresses = ABMultiValueGetCount(addressRef); for (CFIndex i=0; i<numberOfAddresses; i++) { CFStringRef label = ABMultiValueCopyLabelAtIndex(addressRef, i); if (label) { if (CFEqual(label, kABHomeLabel)) { NSDictionary *addressDict = (__bridge NSDictionary *)ABMultiValueCopyValueAtIndex(addressRef, 0); [contactInfoDict setObject:[addressDict objectForKey:(NSString *)kABPersonAddressStreetKey] forKey:@"homeAddress"]; [contactInfoDict setObject:[addressDict objectForKey:(NSString *)kABPersonAddressZIPKey] forKey:@"homeZipCode"]; [contactInfoDict setObject:[addressDict objectForKey:(NSString *)kABPersonAddressCityKey] forKey:@"homeCity"]; } else if (CFEqual(label, kABWorkLabel)) { NSDictionary *addressDict = (__bridge NSDictionary *)ABMultiValueCopyValueAtIndex(addressRef, 0); [contactInfoDict setObject:[addressDict objectForKey:(NSString *)kABPersonAddressStreetKey] forKey:@"workAddress"]; [contactInfoDict setObject:[addressDict objectForKey:(NSString *)kABPersonAddressZIPKey] forKey:@"workZipCode"]; [contactInfoDict setObject:[addressDict objectForKey:(NSString *)kABPersonAddressCityKey] forKey:@"workCity"]; } CFRelease(label); } } } CFRelease(addressRef); // If the contact has an image then get it too. if (ABPersonHasImageData(person)) { NSData *contactImageData = (__bridge NSData *)ABPersonCopyImageDataWithFormat(person, kABPersonImageFormatThumbnail); [contactInfoDict setObject:contactImageData forKey:@"image"]; } return contactInfoDict; } // Displays the information of a selected person - (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person { // only returns a few fields, and none of the multi value ones :-( NSMutableDictionary *results = [QuickContactsViewController convertABRecordRef:person]; ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL); ABRecordID foundId = ABRecordGetRecordID(person); ABRecordRef fullPerson = ABAddressBookGetPersonWithRecordID(addressBook, foundId); // also only returns a few fields!? NSMutableDictionary *selectedFromID = [QuickContactsViewController convertABRecordRef:fullPerson]; return YES; } // Does not allow users to perform default actions such as dialing a phone number, when they select a person property. - (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier { // returns all simple and multi-value fields!? NSMutableDictionary *results = [QuickContactsViewController convertABRecordRef:person]; return NO; }
EDIT: adding my solution (thanks Thorsten!).
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person { NSArray *allPersonRecords = (NSArray *)CFBridgingRelease(ABPersonCopyArrayOfAllLinkedPeople(person)); NSLog(@"Count Linked People: %i", allPersonRecords.count); NSMutableDictionary *results = [[NSMutableDictionary alloc]initWithCapacity:12]; for (int x=0; x<[allPersonRecords count]; x++) { ABRecordRef user = CFBridgingRetain([allPersonRecords objectAtIndex:x]); NSMutableDictionary *userFromArray = [QuickContactsViewController convertABRecordRef:user]; [results addEntriesFromDictionary:userFromArray];