I'm having a bit of an issue with using the address book to get the names of the contacts from the device into my own contacts view within my application.
The code I have works fine on the emulator but I when tested on an iPhone 4 it will crash, the application seems to work fine if there are two or less contacts but 3 or more and the application crashes.
Here is the code I am using to get the names of contacts into an array.
ABAddressBookRef addressBook;
bool wantToSaveChanges = YES;
bool didSave;
CFErrorRef error = NULL;
addressBook = ABAddressBookCreate();
listOfContacts = [[NSMutableArray alloc]init];
int i;
int len = (int) ABAddressBookGetPersonCount(addressBook);
for(i = 1; i< (len+1); i++){
ABRecordRef person = ABAddressBookGetPersonWithRecordID(addressBook, (ABRecordID) i);
NSString* name = (NSString *)ABRecordCopyCompositeName(person);
ABMultiValueRef number = (NSString *)ABRecordCopyValue(person,kABPersonPhoneProperty);
NSString *mobileNum = (NSString *)ABMultiValueCopyValueAtIndex(number, 0 );
NSLog(#"Name = %#", name);
NSLog(#"Number = %#", mobileNum);
[listOfContacts addObject:name];
[name release];
[mobileNum release];
}
if(ABAddressBookHasUnsavedChanges(addressBook)){
if(wantToSaveChanges){
didSave = ABAddressBookSave(addressBook, &error);
if(!didSave){
//Error
}
}
else{
ABAddressBookRevert(addressBook);
}
}
When it crashes this is the line that gets highlighted in Xcode:
NSString* name = (NSString *)ABRecordCopyCompositeName(person);
And the error states:
Thread 1: Program received signal: "EXC_BAD_ACCESS"
Can anyone see what the problem might be? I dont understand why it would work on the emulator but not on the device? And also why it works for up to two contacts but not 3 or more??
Just a guess:
ABRecordRef person = ABAddressBookGetPersonWithRecordID(addressBook, (ABRecordID) i);
This line looks fishy for me. I doubt that the record IDs are numbered from 1 to whatever. Especially if you have deleted an entry.
This would explain why it works on the simulator, I guess you just added some test contacts and never deleted one.
Here is how I solved it:
(Note how I make sure to call only active records, also I created a customized Contact class)
This code takes care of edge case like: email/phone doesn't exist, or exists more than once...
+(NSArray *)getAddressBook{
ABAddressBookRef addressBook;
bool wantToSaveChanges = YES;
bool didSave;
CFErrorRef error = NULL;
addressBook = ABAddressBookCreate();
NSMutableArray *listOfContacts = [[NSMutableArray alloc]init];
CFArrayRef array=ABAddressBookCopyArrayOfAllPeople(addressBook);
int len=CFArrayGetCount(array);
for (int i = 0; i<len; i++){
ABRecordRef person = CFArrayGetValueAtIndex(array, i);
if (ABRecordGetRecordType(person)==kABPersonType){
NSString *firstName = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *lastName = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
ABMultiValueRef emails = (ABMultiValueRef)ABRecordCopyValue(person,kABPersonEmailProperty);
ABMultiValueRef numbers = (ABMultiValueRef)ABRecordCopyValue(person,kABPersonPhoneProperty);
int sumEmails=ABMultiValueGetCount(emails);
int sumNumbers=ABMultiValueGetCount(numbers);
for (int j=0; j<(sumNumbers>sumEmails?sumNumbers:sumEmails); j++) {
ACL_AB_Contact *contact=[[ACL_AB_Contact alloc]initWithFirstName:firstName LastName:lastName];
if (j<sumEmails){
contact.emailAddress=(NSString *)ABMultiValueCopyValueAtIndex(emails,j);
}
if (j<sumNumbers){
contact.phoneNumber=(NSString *)ABMultiValueCopyValueAtIndex(numbers,j);
}
[contact logContact];
[listOfContacts addObject:contact];
[contact release];
}
}
}
if(ABAddressBookHasUnsavedChanges(addressBook)){
if(wantToSaveChanges){
didSave = ABAddressBookSave(addressBook, &error);
if(!didSave){
//Error
}
}
else{
ABAddressBookRevert(addressBook);
}
}
return [listOfContacts autorelease];
}
The record IDs are dynamic. It means that if you add 2 contacts and then remove the first, you will have only a contact with id "2". So I wouln't use a for statement to get through the contacts. Follow the Address Book Programming Guide
Related
I have some code to copy contacts from Addreesbook. It works perfectly if there is a small number of contacts. Now In my phone there us 1200 contacts and the app crashes when I tried to copy them. Can anyone help me to optimize this code or rewrite code? The code I am using is added below:
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);
NSString *requestContactsString = #"<contacts>";
for (int i=0; i<nPeople; i++)
{
NSLog(#"Started : %d", i);
ABRecordRef ref = CFArrayGetValueAtIndex(allPeople, i);
CFTypeRef firstName = ABRecordCopyValue(ref, kABPersonFirstNameProperty);
CFTypeRef lastName = ABRecordCopyValue(ref, kABPersonLastNameProperty);
CFTypeRef email = ABRecordCopyValue(ref, kABPersonEmailProperty);
CFTypeRef phone = ABRecordCopyValue(ref, kABPersonPhoneProperty);
requestContactsString = [requestContactsString stringByAppendingFormat:#"<item>"];
if(firstName)
{
requestContactsString = [requestContactsString stringByAppendingFormat:#"<firstname>%#</firstname>", firstName];
CFRelease(firstName);
firstName = nil;
}
if(lastName)
{
requestContactsString = [requestContactsString stringByAppendingFormat:#"<lastname>%#</lastname>", lastName];
CFRelease(lastName);
lastName = nil;
}
if(email)
{
if(ABMultiValueGetCount(email)>0)
{
CFTypeRef em = ABMultiValueCopyValueAtIndex(email, 0);
requestContactsString = [requestContactsString stringByAppendingFormat:#"<email>%#</email>", em];
CFRelease(em);
}
CFRelease(email);
email = nil;
}
if(phone)
{
if(ABMultiValueGetCount(phone)>0)
{
CFTypeRef ph = ABMultiValueCopyValueAtIndex(phone, 0);
requestContactsString = [requestContactsString stringByAppendingFormat:#"<phone>%#</phone>", ph];
CFRelease(ph);
}
CFRelease(phone);
phone = nil;
}
requestContactsString = [requestContactsString stringByAppendingFormat:#"</item>"];
}
if(allPeople)
{
CFRelease(allPeople);
allPeople = nil;
}
if(addressBook)
{
CFRelease(addressBook);
addressBook = nil;
}
requestContactsString = [requestContactsString stringByAppendingFormat:#"</contacts>"];
NSString *hashedContactsString = [self generateHashedPassword:requestContactsString];
The major inefficiency I can see is the use of [NSString stringByAppendingFormat] which is creating a new NSString object each time it's called. This means that you have a large number of long auto-released NSString objects that are no longer being used until the next run loop (unless you are using ARC, in which case the situation is probably better).
I think you would make much better use of memory, and get better performance, by making requestContactsString an NSMutableString and using [NSMutableString appendString] (reference) instead. This would modify the existing object, allocating more memory to accept the new string.
Each append would look like this:
[requestContactsString appendString:[NSString stringWithFormat:#"<lastname>%#</lastname>", lastName]];
Which still creates a large number of auto-released objects, but they are much smaller.
Use a NSMutableString and build your XML with the appendFormat: method. In your loop you are copying the whole string you've assembled so far multiple times.
I'm trying to get the Twitter username from a contact in the address book but I'm not able to do it
I'm using this code that I found "googling":
- (NSString*)getTwitterUsernameFromRecord:(ABRecordRef)record {
NSString * twitterUsername = nil;
ABMultiValueRef socials = ABRecordCopyValue(record, kABPersonSocialProfileProperty);
if (!socials) {
return nil;
}
CFIndex socialsCount = ABMultiValueGetCount(socials);
for (int k=0 ; k<socialsCount ; k++) {
CFDictionaryRef socialValue = ABMultiValueCopyValueAtIndex(socials, k);
if(CFStringCompare( CFDictionaryGetValue(socialValue, kABPersonSocialProfileServiceKey), kABPersonSocialProfileServiceTwitter, 0)==kCFCompareEqualTo) {
twitterUsername = (NSString*) CFDictionaryGetValue(socialValue, kABPersonSocialProfileUsernameKey);
}
CFRelease(socialValue);
if(twitterUsername)
break;
}
CFRelease(socials);
return twitterUsername;
}
I've put the if to validate the "socials" array is nil because I was getting exception when trying to get "socials" array count.
I've tried this code in the simulator and in a real device with contacts which twitter info is filled but the "socials" array I get is always nil. I'm getting the wrong property from the record? Any help?
Thank you in advance
your function works for me almost as-is, aside from needing to bridge cast the twitter username (which is just an ARC thing). how are you accessing the address book?
when i use this code to call your function:
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex contactCount = ABAddressBookGetPersonCount(addressBook);
for( int i = 0; i<contactCount; i++ )
{
ABRecordRef ref = CFArrayGetValueAtIndex(people, i);
NSString *twitterHandle = [self getTwitterUsernameFromRecord:ref];
if (twitterHandle) {
NSLog(#"record %d has twitter name %#", i, twitterHandle);
}
}
i get this console output:
2012-06-26 12:09:40.864 AddressBookTest[2246:707] record 57 has twitter name alexshepard
I am trying to get all the phone number from the Phone Book of iPhone. But my app crashes as soon as I try to fetch the number using kABPersonPhoneProperty. The code snippet is here
ABAddressBookRef addressBook = ABAddressBookCreate();
NSMutableArray *allPeople = (NSMutableArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
int nPeople = ABAddressBookGetPersonCount(addressBook);
CFRelease(addressBook);
for(int i=0; i < nPeople; i++ ){
ABRecordRef person = [allPeople objectAtIndex:i];
NSString *name = #"";
CFTypeRef fName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
CFTypeRef lName = ABRecordCopyValue(person, kABPersonLastNameProperty);
ABMultiValueRef multi = ABRecordCopyValue(person , kABPersonPhoneProperty);
NSString *number = (NSString *)ABMultiValueCopyValueAtIndex(multi, 0);
if ([number length] > 0) {
if(fName != NULL)
{
if (lName != NULL) {
name = [[NSString stringWithFormat:#"%#", fName] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
else {
name = [[NSString stringWithFormat:#"%#", fName] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
NSLog(#"Number==>%# ",number);
}
}
if (fName != NULL) {
CFRelease(fName);
}
if (lName != NULL) {
CFRelease(lName);
}
if (multi != NULL) {
CFRelease(multi);
}
}
[allPeople release];
I am unable to figure out the error in this. I am also releasing everything even then it is not running smoothly.
I am even getting potential memory leak when running build and analyze for the code part
ABMultiValueRef multi = ABRecordCopyValue(person , kABPersonPhoneProperty);
NSLog(#"Number===>%d" , ABMultiValueGetCount(multi));
Please help me out to come.
Any kind of help would be highly appreciated.
Thanks in advance
Thanks for the response. I have figured out the solution fot the problem here. I did not release number variable thinking that the reference was passed as reference but it is copied to the returned location, hence need to release number after the usage.
thanks again for all the responses!!
I have an app that displays ABAddressBook contacts in a UITableView. Currently I'm reading the contacts into an NSDictionary, however this appears to crash for some users, which I suspect is a memory issue.
Is there another approach to display ABAddressBook contacts in a UITableView without either first storing them in an NSDictionary or using ABPeoplePicker?
A different way using ARC:
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef addressBookData = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex count = CFArrayGetCount(addressBookData);
NSMutableArray *contactsArray = [NSMutableArray new];
for (CFIndex idx = 0; idx < count; idx++) {
ABRecordRef person = CFArrayGetValueAtIndex(addressBookData, idx);
NSString *firstName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
if (firstName) {
NSDictionary *dict = [NSDictionary dictionaryWithObject:firstName ForKey:#"name"];
[contactsArray addObject:dict];
}
}
CFRelease(addressBook);
CFRelease(addressBookData);
You can use following way,
ABAddressBookRef ab = ABAddressBookCreateWithOptions(NULL, NULL);
NSArray *arrTemp = (NSArray *)ABAddressBookCopyArrayOfAllPeople(ab);
The above 2 lines will create an array for all your contacts on the iPhone.
Now whatever property of a contact you want to display you can display by using the below code. For example, I want to display the first name of all contacts and then create one Mutable array called it arrContact.
NSMutableArray *arrContact = [[NSMutableArray alloc] init];
for (int i = 0; i < [arrTemp count]; i++)
{
NSMutableDictionary *dicContact = [[NSMutableDictionary alloc] init];
NSString *str = (NSString *) ABRecordCopyValue([arrTemp objectAtIndex:i], kABPersonFirstNameProperty);
#try
{
[dicContact setObject:str forKey:#"name"];
}
#catch (NSException * e) {
[dicContact release];
continue;
}
[arrContact addObject:dicContact];
[dicContact release];
}
Now just display it using the arrContact array in a table view..
Same as Abizern's answer, but if you want to display full names that are localized, use ABRecordCopyCompositeName. (In English names are "First Last", but in Chinese names are "LastFirst").
ABRecordRef person = CFArrayGetValueAtIndex(addressBookData, idx);
NSString *fullName = (__bridge_transfer NSString *)ABRecordCopyCompositeName(person);//important for localization
I have been able to use the SDK to access Address Book contacts for people, but whenever I pick a contact who is a company, my app crashes.
does anyone know the property for the company field?
does anyone know the code to make it work?
thanks in advance
It seems your issue is not related to company tag itself. AB fields need to be managed carefully, released when not used and checked for nil all the time, many fields are not set.
In principle, here is example of how I use it. This function loads contacts (those fields interested for my app) and returns array with contacts to the caller. Hope this is helpful. Note how I release not needed refs.
- (NSArray *)loadContacts
{
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);
NSMutableArray *contacts = [NSMutableArray array];
for (int i = 0 ; i < nPeople ; i++ ) {
ABRecordRef person = CFArrayGetValueAtIndex(allPeople, i);
NSString *firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *lastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
NSString *fullName;
if (firstName && lastName) {
fullName = [NSString stringWithFormat:#"%# %#", firstName, lastName];
} else {
if (firstName) {
fullName = [NSString stringWithString:firstName];
} else if (lastName) {
fullName = [NSString stringWithString:lastName];
} else {
continue;
}
}
NSString *email = nil;
ABMultiValueRef emails = ABRecordCopyValue(person, kABPersonEmailProperty);
if (emails) {
NSArray *emailAddresses = [(NSArray *)ABMultiValueCopyArrayOfAllValues(emails) autorelease];
if (emailAddresses && [emailAddresses count] > 0)
email = [emailAddresses objectAtIndex:0];
CFRelease(emails);
}
if (email) {
NSDictionary *contact = [NSDictionary
dictionaryWithObjectsAndKeys:fullName, #"name",
email, #"email", nil];
[contacts addObject:contact];
}
if (firstName)
CFRelease(firstName);
if (lastName)
CFRelease(lastName);
}
CFRelease(allPeople);
CFRelease(addressBook);
return contacts;
}
you need to use kABPersonOrganizationProperty property for get the company name from addressbook