22

All I want to do is let the user select a number from the address book. I found the code in this question:

How to get a Phone Number from an Address Book Contact (iphone sdk)

ABMultiValueRef container = ABRecordCopyValue(person, property);
CFStringRef contactData = ABMultiValueCopyValueAtIndex(container, identifier);
CFRelease(container);
NSString *contactString = [NSString stringWithString:(NSString *)contactData];
CFRelease(contactData);

The problem is that on the second line (when running on a 3.0 device) I get the following error:

Account Manager could not find account with identifier MobileMe:rustyshelf

followed by:

Program received signal: "EXC_BAD_ACCESS".

This is all inside the picker delegate method:

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier{

This is just one of the contacts in my address book, which is synched with Mobile Me

Edit: I think this might be a bug with the SDK, it happens for some of my contacts but not for others...

Community
  • 1
  • 1
rustyshelf
  • 44,963
  • 37
  • 98
  • 104

7 Answers7

29

The "identifier" argument does not hold the CFIndex of the record touched. The method I use to tell which phone number a user selected is this:

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
    if (property == kABPersonPhoneProperty) {
        ABMultiValueRef multiPhones = ABRecordCopyValue(person, kABPersonPhoneProperty);
        for(CFIndex i = 0; i < ABMultiValueGetCount(multiPhones); i++) {
            if(identifier == ABMultiValueGetIdentifierAtIndex (multiPhones, i)) {
                CFStringRef phoneNumberRef = ABMultiValueCopyValueAtIndex(multiPhones, i);
                CFRelease(multiPhones);
                NSString *phoneNumber = (NSString *) phoneNumberRef;
                CFRelease(phoneNumberRef);
                txtPhoneNumber.text = [NSString stringWithFormat:@"%@", phoneNumber];
                [phoneNumber release];
            }
        }
    }

    [self dismissModalViewControllerAnimated:YES];
    return NO;
}
  • 1
    watch out for overrelease - "multiPhones" may accidental be released if the person has multiple phone numbers. Also "phoneNumber" and "phoneNumberRef". – neoneye Aug 20 '11 at 21:04
  • 1
    Yes, you should edit your post. here is a better for loop : for(CFIndex i = 0; i < ABMultiValueGetCount(multiPhones); i++) { NSString *phoneNumber = (NSString *)ABMultiValueCopyValueAtIndex(multiPhones, i); NSLog(@"PHONE : %@", phoneNumber); [phoneNumber release]; } – Martin Mar 22 '12 at 10:52
  • why are you releasing multiphones in the for loop? – Jonathan. Mar 30 '13 at 02:21
16

Going off of JWDs answer, here is a safer version that uses the builtin constants and chooses the iphone number over another mobile number...

ABMultiValueRef phones =(NSString*)ABRecordCopyValue(person, kABPersonPhoneProperty);
NSString* mobile=@"";
NSString* mobileLabel;
for(CFIndex i = 0; i < ABMultiValueGetCount(phones); i++) {
    mobileLabel = (NSString*)ABMultiValueCopyLabelAtIndex(phones, i);
    if([mobileLabel isEqualToString:(NSString *)kABPersonPhoneMobileLabel])
    {
        [mobile release] ;
        mobile = (NSString*)ABMultiValueCopyValueAtIndex(phones, i);
    }
    else if ([mobileLabel isEqualToString:(NSString*)kABPersonPhoneIPhoneLabel])
    {
        [mobile release] ;
        mobile = (NSString*)ABMultiValueCopyValueAtIndex(phones, i);
        break ;
    }
}
dr_pepper
  • 1,587
  • 13
  • 28
6
- (BOOL)personViewController:(ABPersonViewController *)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue
{
   if (property == kABPersonPhoneProperty)
   {
       ABMultiValueRef numbers = ABRecordCopyValue(person, property);
       NSString* targetNumber = (__bridge NSString *) ABMultiValueCopyValueAtIndex(numbers, ABMultiValueGetIndexForIdentifier(numbers, identifierForValue));

       NSLog(@"%@", targetNumber);
   }
   return NO;
}

I used this question to construct my own solution. The code I'm posting is not intended as answer, merely for someone to maybe find useful. These are the simple steps for get a property. Memory management excluded.

Yunus Nedim Mehel
  • 12,089
  • 4
  • 50
  • 56
r712m
  • 299
  • 3
  • 7
  • 2
    +1 Hey, this works! My problem is when I selected phone numbers labelled "mobile", "home" or "work", my app is fine but when "iPhone" is selected, my app crashed. Your answer saved my day. Thanks. – Rick Aug 29 '12 at 16:51
  • This code leaks memory. First the `numbers` is never released, you should call `CFRelease(numbers)` after you've done using it. Second the `targetNumber` is never released. You should use `__bridge_transfer` instead of simple `__bridge` to fix that. – SnakE Mar 02 '15 at 17:40
4

I use this to pull the mobile number from an ABRecordRef/ The "record" variable is the ABRecordRef you want the phone number for. Where I have "" you can use another phone tag string to find other types of phone numbers.

//Get mobile phone number
ABMultiValueRef phones =(NSString*)ABRecordCopyValue(record, kABPersonPhoneProperty);
NSString* mobile=@"";
NSString* mobileLabel;
for(CFIndex i = 0; i < ABMultiValueGetCount(phones); i++) {
    mobileLabel = (NSString*)ABMultiValueCopyLabelAtIndex(phones, i);
    if([mobileLabel isEqualToString:@"_$!<Mobile>!$_"]) {
          mobile = (NSString*)ABMultiValueCopyValueAtIndex(phones, i);
    }
}
JWD
  • 12,188
  • 3
  • 34
  • 32
  • Wouldn't that code fail to find mobile numbers that are tagged with the 'iPhone' tag. In Australia at least that seems to happen automatically for contacts received via MMS. – rustyshelf Jul 14 '09 at 13:09
  • You are right, the code I posted is just searching for the numbers tagged "_$!!$_". It is also looping over all results in the for loop, so you could easily change "_$!!$_" to "iPhone" and it should work. – JWD Jul 14 '09 at 13:35
3

You should be able to just do the following:

ABMultiValueRef phoneNumbers = (ABMultiValueRef)ABRecordCopyValue(person, kABPersonPhoneProperty);
CFRelease(phoneNumbers);
NSString* phoneNumber = (NSString*)ABMultiValueCopyValueAtIndex(phoneNumbers, 0);

Of course, that's only going to get you the first number of (potentially) many associated with the person who's been selected.

Nathan de Vries
  • 15,481
  • 4
  • 49
  • 55
  • thanks for the answer, but I just want to find the number that the user clicked on. I think my code is correct, but it sounds like there's a bug in firmware 3.0 stopping me from getting it. The code above works for some of my contacts, but not for others. – rustyshelf Jul 14 '09 at 13:10
  • 1
    @Nathan: what if a contact doesn't have a phonenumber? – MaikelS Oct 19 '11 at 12:16
1

you can also use this "ABMultiValueGetIndexForIdentifier" like in this code :

ABPropertyType pt = ABPersonGetTypeOfProperty(property);
NSString *phoneNumber;
if ((pt & kABMultiValueMask) == kABMultiValueMask) {
        ABMultiValueRef phoneProperty = ABRecordCopyValue(person,property);
        CFIndex idx = ABMultiValueGetIndexForIdentifier(phoneProperty, identifier);
        phoneNumber = (NSString *)ABMultiValueCopyValueAtIndex(phoneProperty,idx);
        CFRelease(phoneProperty);
    } 
j0n
  • 101
  • 1
  • 4
1

Going off from Dan's answer :

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person 
                                property:(ABPropertyID)property 
                              identifier:(ABMultiValueIdentifier)identifier
{
    if (property == kABPersonPhoneProperty) { // if tapped is equal to a phone property
        CFStringRef cfnumber;
        ABMultiValueRef numbers = ABRecordCopyValue(person, kABPersonPhoneProperty); 
        for(CFIndex i = 0; i < ABMultiValueGetCount(numbers); i++) {
            if(identifier == ABMultiValueGetIdentifierAtIndex (numbers, i)) { //if tapped number identifier is the same as identifier number tapped
                cfnumber = ABMultiValueCopyValueAtIndex(numbers, i); // copy the number to CFSTRING number
            }
        }        
        NSString *number = [NSString stringWithFormat:@"%@",cfnumber];
        CFRelease(cfnumber); 
        //do anything you want with the number
    }
    return NO;
}

Can't tell you if it's the best/correct way. But i ran into some errors using Dan's code.. hence i've decided to share it after figuring it out. Still learning objective c.. haha.. Hope it helps..

Regards, Steve0hh

steve0hh
  • 637
  • 6
  • 15