I cannot seem to structure this method so that when I analyse the project it doesn't complain.
It is complaining about how I release people object.
- (NSArray *)getAllContacts {
NSMutableArray *result = [NSMutableArray array];
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFRelease(addressBook);
NSArray *peopleArray = (NSArray *)people;
// Return if there are no contacts in the address book
if (peopleArray && peopleArray.count > 0) {
for (int i = 0; i <= peopleArray.count -1; i++) {
ABRecordRef person = [peopleArray objectAtIndex:i];
ABRecordID sourceID = ABRecordGetRecordID(person);
TableViewControllerItem *item = [AddressBookModel createTableViewControllerItemFromABRecordID:[NSString stringWithFormat:#"%i", sourceID]];
[result addObject:item];
}
CFRelease(people); //If I put the release here I get a potential leak of people
}
CFRelease(people); //If I put the release here I get a null pointer argument in call to CFRelease
return [NSArray arrayWithArray:result];
}
// Remove the CFRelease() inside the if-block
And modify the CFRelease() before the return statement to be like this,
if (peopleArray) CFRelease(people);
return [NSArray arrayWithArray:result];
Related
I have a question regarding the predicate I am using in the following code
NSMutableArray *records = (__bridge NSMutableArray *)ABAddressBookCopyArrayOfAllPeople( addressBook );
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"record.phoneNumber contains %#",#"123"];
#try {
[records filterUsingPredicate:predicate];
}
#catch (NSException *exception) {
NSLog(#"%#",exception);
}
#finally {
//
}
The exception I get is:
[<__NSCFType 0x6e2c5e0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key record.
I've been trying to find a guide on predicates for address book with no luck though. Any suggestions?
You can't filter the address book using NSPredicates. Additionally, phoneNumber is not a field of ABRecordRef. Users can have multiple phone numbers, so you need to inspect each one.
You would do something like this:
CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
NSMutableArray *matchingPeople = [NSMutableArray array];
for (CFIndex i = 0; i < CFArrayGetCount(people); i++) {
ABRecordRef person = CFArrayGetValueAtIndex(people, i);
ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
int phoneNumbers = ABMultiValueGetCount(phones);
if (phoneNumbers > 0) {
for (CFIndex i = 0; i < phoneNumbers; i++) {
NSString *phone = (NSString *)CFBridgingRelease(ABMultiValueCopyValueAtIndex(phones, i));
if ([phone rangeOfString:#"123"].location != NSNotFound) {
[matchingPeople addObject:person];
break;
}
}
}
CFRelease(phones);
}
CFRelease(people);
Personally, I wouldn't add ABRecordRefs to the array--I'd create a value object that includes just the fields you want from the record and add that, so when you're done with the loop, you can ensure you don't have any dangling CFTypes.
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
I want to search the iPhone address book for a specific phone number, and then retrieve the contact name. I am currently looping through all contacts and extracting the multivalue properties and comparing against the value. This is taking way too much time. I have read the Apple addressbook guide, and they say:
"accomplish other kinds of searches, use the function
ABAddressBookCopyArrayOfAllPeople and then filter the results using
the NSArray method filteredArrayUsingPredicate:."
Can anyone give me an example on how to exactly do that?
Thanks.
If you want to do a search in the contacts with phone number, then I will tell you one suggestion.
1.Get all contacts
NSArray *thePeoples = (NSArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
2.Create another array(records) from the contacts array(thePeoples),
records:[
record1, record2, ....recordN ]
record: {name:"myContactName",
phoneNumber:"1234567890"}
3.Search the mutableArray(records) with predicate.
NSPredicate * myPredicate = [NSPredicate predicateWithFormat:#"record.phoneNumber contains %#",string];
NSArray * filteredArray = [records filteredArrayUsingPredicate:myPredicate];
This is just a simple example to your solution, and remember phoneNumber is a multiValue field. So we will include an array as phone number in the model class variable.
The following method will return an array that contains all of the contacts that have the given phone number. This method took 0.02 seconds to search 250 contacts on my iPhone 5 running iOS7.
#import <AddressBook/AddressBook.h>
-(NSArray *)contactsContainingPhoneNumber:(NSString *)phoneNumber {
/*
Returns an array of contacts that contain the phone number
*/
// Remove non numeric characters from the phone number
phoneNumber = [[phoneNumber componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] componentsJoinedByString:#""];
// Create a new address book object with data from the Address Book database
CFErrorRef error = nil;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
if (!addressBook) {
return [NSArray array];
} else if (error) {
CFRelease(addressBook);
return [NSArray array];
}
// Requests access to address book data from the user
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {});
// Build a predicate that searches for contacts that contain the phone number
NSPredicate *predicate = [NSPredicate predicateWithBlock: ^(id record, NSDictionary *bindings) {
ABMultiValueRef phoneNumbers = ABRecordCopyValue( (__bridge ABRecordRef)record, kABPersonPhoneProperty);
BOOL result = NO;
for (CFIndex i = 0; i < ABMultiValueGetCount(phoneNumbers); i++) {
NSString *contactPhoneNumber = (__bridge_transfer NSString *) ABMultiValueCopyValueAtIndex(phoneNumbers, i);
contactPhoneNumber = [[contactPhoneNumber componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] componentsJoinedByString:#""];
if ([contactPhoneNumber rangeOfString:phoneNumber].location != NSNotFound) {
result = YES;
break;
}
}
CFRelease(phoneNumbers);
return result;
}];
// Search the users contacts for contacts that contain the phone number
NSArray *allPeople = (NSArray *)CFBridgingRelease(ABAddressBookCopyArrayOfAllPeople(addressBook));
NSArray *filteredContacts = [allPeople filteredArrayUsingPredicate:predicate];
CFRelease(addressBook);
return filteredContacts;
}
Use This. this is my code.
Make Array for searching.
NSLog(#"=====Make People Array with Numbers. Start.");
peopleWithNumber = [[NSMutableDictionary alloc] init];
for (int i=0; i < [people count]; i++) {
NSInteger phoneCount = [self phoneCountAtIndex:i];
if (phoneCount != 0) {
NSMutableArray *phoneNumbers = [[NSMutableArray alloc] init];
for (int j=0 ; j < phoneCount ; j++) {
[phoneNumbers addObject:[self phoneNumberAtIndex:i phoneIndex:j]];
}
[peopleWithNumber addEntriesFromDictionary:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSArray arrayWithArray:phoneNumbers], [self fullNameAtIndex:i], nil]];
}
}
NSLog(#"=====Make People Array with Numbers. End.\n");
Searching method. it(peopleWithNumber) would be faster than using array(people)
"NSArray *people = (NSArray *) ABAddressBookCopyArrayOfAllPeople(addressBook);"
- (NSArray *)searchNamesByNumber:(NSString *)number {
NSString *predicateString = [NSString stringWithFormat:#"%#[SELF] contains '%#'",#"%#",number];
NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:predicateString,peopleWithNumber,number];
NSArray *names = [[peopleWithNumber allKeys] filteredArrayUsingPredicate:searchPredicate];
return names;
}
You cannot use a predicateWithFormat with an array of ABRecordRef opaque types. But you can use predicateWithBlock:
[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
ABRecordRef person=(__bridge ABRecordRef)evaluatedObject;
CFTypeRef theProperty = ABRecordCopyValue(person, kABPersonPhoneProperty);
NSArray *phones = (__bridge_transfer NSArray *) ABMultiValueCopyArrayOfAllValues(theProperty);
CFRelease(theProperty);
BOOL result=NO;
for (NSString *value in phones) {
if ([value rangeOfString:#"3"].location!=NSNotFound) {
result=YES;
break;
}
}
return result;
}];
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 would like to list all phone numbers (or any other field) of people in the addressbook.
I've written the following code:
- (void)addressBookFill{
ABAddressBookRef addressBook = ABAddressBookCreate();
people = (NSArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
[addressBook release];
}
- (void)printAddressBook{
for(id person in people){
NSLog(#"%#", [person class]);
NSLog(#"\t%#", person );
}
}
When I invoke the printAddressBook method I get this on my console:
2010-07-06 10:34:11.998 app[91420:207] __NSCFType
2010-07-06 10:34:11.999 app[91420:207] <CPRecord: 0x5d56ce0 ABPerson>
And I don't have any idea, how to dereference this ABPerson object, how to get any info from it.
I tried:
firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
but I got some exceptions.
Can anybody tell me how to get some info from these objects?
Read the documentation about the ABPerson class:
http://developer.apple.com/mac/library/documentation/UserExperience/Reference/AddressBook/Classes/ABPerson_Class/Reference/Reference.html
and also the ABRecord class:
http://developer.apple.com/mac/library/documentation/UserExperience/Reference/AddressBook/Classes/ABRecord_Class/Reference/Reference.html#//apple_ref/occ/cl/ABRecord
[ person valueForProperty: #"propName" ]
You can get the available properties with:
[ ABPerson properties ]
[ EDIT ]
On iPhone, you can use the following code to access a value:
NSString * lastName = (NSString *)ABRecordCopyValue( person, kABPersonLastNameProperty );
May be this code will help you for iOS5, please like it
ABAddressBookRef addressBook = ABAddressBookCreate();
NSArray *arrayOfAllPeople = (__bridge_transfer NSArray *) ABAddressBookCopyArrayOfAllPeople(addressBook);
NSUInteger peopleCounter = 0;
for (peopleCounter = 0;peopleCounter < [arrayOfAllPeople count]; peopleCounter++){
ABRecordRef thisPerson = (__bridge ABRecordRef) [arrayOfAllPeople objectAtIndex:peopleCounter];
NSString *name = (__bridge_transfer NSString *) ABRecordCopyCompositeName(thisPerson);
NSLog(#"First Name = %#", name);
ABMultiValueRef emails = ABRecordCopyValue(thisPerson, kABPersonEmailProperty);
for (NSUInteger emailCounter = 0; emailCounter < ABMultiValueGetCount(emails); emailCounter++){
/* And then get the email address itself */
NSString *email = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(emails, emailCounter);
NSLog(#"Email : %#",email);
}
}
CFRelease(addressBook);
You want get only those contact who has email on my contact so use this code for iOS5
Please like it if it work
First add AddressBook.framework and #import <AddressBook/AddressBook.h>
- (NSArray*)printAddressBook{
NSMutableArray *mutableData = [NSMutableArray new];
ABAddressBookRef addressBook = ABAddressBookCreate();
NSArray *arrayOfAllPeople = (__bridge_transfer NSArray *) ABAddressBookCopyArrayOfAllPeople(addressBook);
NSUInteger peopleCounter = 0;
for (peopleCounter = 0;peopleCounter < [arrayOfAllPeople count]; peopleCounter++){
ABRecordRef thisPerson = (__bridge ABRecordRef) [arrayOfAllPeople objectAtIndex:peopleCounter];
NSString *name = (__bridge_transfer NSString *) ABRecordCopyCompositeName(thisPerson);
NSLog(#"First Name = %#", name);
ABMultiValueRef emails = ABRecordCopyValue(thisPerson, kABPersonEmailProperty);
for (NSUInteger emailCounter = 0; emailCounter < ABMultiValueGetCount(emails); emailCounter++){
/* And then get the email address itself */
NSString *email = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(emails, emailCounter);
NSLog(#"Email : %#",email);
NSMutableDictionary *personDict = [[NSMutableDictionary alloc] initWithObjectsAndKeys:name,#"name",email,#"email", nil];
[mutableData addObject:personDict];
}
}
CFRelease(addressBook);
return [NSArray arrayWithArray:mutableData];
}