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.
Related
How do I fetch only mobile section record from addressbook in ios?
I want to add only one record to my array that is fetched from mobile section record.
How do I do that. I am getting all the phone property records. but I need to get only mobile section record.
NSArray *allPeople = (NSArray *)ABAddressBookCopyArrayOfAllPeople(myAddressBook);
NSLog(#"allpeople%#", allPeople);
for (id record in allPeople) {
CFTypeRef phoneProperty = ABRecordCopyValue((ABRecordRef)record, kABPersonPhoneProperty);
NSArray *phones = (NSArray *)ABMultiValueCopyArrayOfAllValues(phoneProperty);
NSLog(#"phones %#",phones);
CFRelease(phoneProperty);
NSMutableDictionary *newRecord = [[NSMutableDictionary alloc] init];
NSMutableString *newPhone = [[NSMutableString alloc] init];
for (NSString *phone in phones) {
if(![newPhone isEqualToString:#""])
[newPhone appendString:#", "];
[newPhone appendString:phone];
}
You can try something like this..
ABMultiValueRef phoneNumbers = ABRecordCopyValue(abPerson, kABPersonPhoneProperty);
if (phoneNumbers) {
CFIndex numberOfPhoneNumbers = ABMultiValueGetCount(phoneNumbers);
for (CFIndex i = 0; i < numberOfPhoneNumbers; i++) {
NSString *phone = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(phoneNumbers, i);
CFStringRef label = ABMultiValueCopyLabelAtIndex(phoneNumbers, i);
if (label) {
if (CFEqual(label, kABPersonPhoneMobileLabel)) {
primaryPhoneText.text = phone;
} else {
}
CFRelease(label);
[allPhones addObject:phone]; // allPhones is an array here.
}
}
CFRelease(phoneNumbers);
}
Hope this will help you out..
Enjoy coding..
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];
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 am developing an app that connects to another iphone using bonjour. One of its features is when I connect to the other device it will automatically check if I have the other persons phone number. So my problem is how do I check my address book for the phone number provided by the other device?
Here's an example extracted from one of my address book methods. I wasn't searching by phone number but this gives you an idea have how to move forward with what you need:
- (void) scanAddressBookSample
{
NSUInteger i;
NSUInteger k;
ABAddressBookRef addressBook = ABAddressBookCreate();
NSArray *people = (NSArray *) ABAddressBookCopyArrayOfAllPeople(addressBook);
if ( people==nil )
{
NSLog(#"NO ADDRESS BOOK ENTRIES TO SCAN");
CFRelease(addressBook);
return;
}
for ( i=0; i<[people count]; i++ )
{
ABRecordRef person = (ABRecordRef)[people objectAtIndex:i];
//
// Phone Numbers
//
ABMutableMultiValueRef phoneNumbers = ABRecordCopyValue(person, kABPersonPhoneProperty);
CFIndex phoneNumberCount = ABMultiValueGetCount( phoneNumbers );
for ( k=0; k<phoneNumberCount; k++ )
{
CFStringRef phoneNumberLabel = ABMultiValueCopyLabelAtIndex( phoneNumbers, k );
CFStringRef phoneNumberValue = ABMultiValueCopyValueAtIndex( phoneNumbers, k );
CFStringRef phoneNumberLocalizedLabel = ABAddressBookCopyLocalizedLabel( phoneNumberLabel ); // converts "_$!<Work>!$_" to "work" and "_$!<Mobile>!$_" to "mobile"
// Find the ones you want here
//
NSLog(#"-----PHONE ENTRY -> %# : %#", phoneNumberLocalizedLabel, phoneNumberValue );
CFRelease(phoneNumberLocalizedLabel);
CFRelease(phoneNumberLabel);
CFRelease(phoneNumberValue);
}
}
[people release];
CFRelease(addressBook);
}
-(void)createQuickAccessContacts{
NSMutableDictionary contactDictionary= [[NSMutableDictionary alloc]init];
CFArrayRef all = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex n = ABAddressBookGetPersonCount(addressBook);
NSDate *date=[NSDate date];
for( int i = 0 ; i < n ; i++ )
{
ABRecordRef ref = CFArrayGetValueAtIndex(all, i);
ABMultiValueRef phones = (ABMultiValueRef)ABRecordCopyValue(ref, kABPersonPhoneProperty);
for(CFIndex j = 0; j < ABMultiValueGetCount(phones); j++)
{
CFStringRef phoneNumberRef = ABMultiValueCopyValueAtIndex(phones, j);
NSString *phoneNumber = (__bridge NSString *)phoneNumberRef;
[contactDictionary setObject:(__bridge id)(ref) forKey:phoneNumber];
}
}
NSLog(#" Time taken %f for %i contacts",[[NSDate date] timeIntervalSinceDate:date],[contactDictionary count]);
}
This takes like 0.5 sec for 2.5k contacts to fill
then you can find the contact using number
ABRecordRef ref= [contactDictionary objectForKey:#"89xxxxxxx"];
its super fast takes like 0.000x seconds
Use This. this is my code.
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");
search method. it would be faster than using array
"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;
}