iOS address book on different queue - ios

Below is some of my code that doesn't block the main thread when loading in contacts to a tableview. It seems to work fine but I am wondering if this will eventually cause some erradic behavior after dispatch_async is called following if(accessGranted). If what I am doing is good practice then maybe this will help some people because I have seen a lot of people with questions about AB with many different answers (probably due to the poor documentation of AB) but if it's not then hopefully someone can tell me what I am doing wrong.
My understanding is that this would be thread safe because it is all being done on the same thread.
CFErrorRef error = nil;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
__block BOOL accessGranted = NO;
if (ABAddressBookRequestAccessWithCompletion != NULL) { // we're on iOS 6 or later
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
accessGranted = granted;
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
else { // we're on iOS 5 or older
accessGranted = YES;
}
if (accessGranted) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
CFArrayRef contacts = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFMutableArrayRef peopleMutable = CFArrayCreateMutableCopy(kCFAllocatorDefault,
CFArrayGetCount(contacts),
contacts);
CFArraySortValues(peopleMutable,
CFRangeMake(0, CFArrayGetCount(peopleMutable)),
(CFComparatorFunction) ABPersonComparePeopleByName,
kABPersonSortByFirstName);
CFRelease(contacts);
for (CFIndex i = 0; i < CFArrayGetCount(peopleMutable); i++)
{
ABRecordRef record = CFArrayGetValueAtIndex(peopleMutable, i);
ABMultiValueRef phoneNumberProperty = ABRecordCopyValue(record, kABPersonPhoneProperty);
if (CFBridgingRelease(ABMultiValueCopyValueAtIndex(phoneNumberProperty, 0)) != nil) {
NSString *firstName = CFBridgingRelease(ABRecordCopyValue(record, kABPersonFirstNameProperty));
NSString *lastName = CFBridgingRelease(ABRecordCopyValue(record, kABPersonLastNameProperty));
NSString *phoneNumbers = (__bridge NSString*)ABMultiValueCopyValueAtIndex(phoneNumberProperty, 0);
}
CFRelease(phoneNumberProperty);
}
CFRelease(peopleMutable);
dispatch_async(dispatch_get_main_queue(), ^{
// Update UI
[self.tableView reloadData];
});
});
}

Related

ABAddressBookCopyArrayOfAllPeople Slow

I am writing an app where I need to read the user’s address book and display a list of all his contacts. The iPhone I’m testing with has ~ 100 contacts and it takes really much time to load the contacts.
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
NSArray *allContacts = (__bridge NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBook);
ABMultiValueRef phones = NULL;
ABRecordRef person = NULL;
for (int i =0; i < allContacts.count; i++) {
person = (__bridge ABRecordRef)([allContacts objectAtIndex:i]);
if (person != nil) {
phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
if (ABMultiValueGetCount(phones) == 0) {
CFErrorRef error = nil;
ABAddressBookRemoveRecord(addressBook, person, &error);
}
CFRelease(phones);
}
}
CFErrorRef saveError = nil;
ABAddressBookSave(addressBook, &saveError);
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.view.backgroundColor=[UIColor clearColor];
picker.peoplePickerDelegate = self;
picker.delegate=self;
NSArray *displayedItems =
[NSArray arrayWithObject:[NSNumber
numberWithInt:kABPersonPhoneProperty]];
picker.displayedProperties = displayedItems;
You can perform the copying in a background thread using performSelectorInBackground:withObject:, that way it won't affect the main thread and you don't have to wait in the UI.

ABRecordCopyValue crash with SIGSEGV

I am trying to get contacts list like this:
CFErrorRef *error = nil;
ABAddressBookRef addressBook = nil;
__block BOOL accessGranted = NO;
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined ||
ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
addressBook = ABAddressBookCreateWithOptions(NULL, error);
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
accessGranted = granted;
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusDenied ||
ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusRestricted) {
return nil;
}
if (!accessGranted) {
if (addressBook) CFRelease(addressBook);
return nil;
}
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);
if (nPeople <= 0) {
CFRelease(addressBook);
return nil;
}
ABRecordRef source = ABAddressBookCopyDefaultSource(addressBook);
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering(addressBook, source, kABPersonSortByFirstName);
if (!allPeople) return nil;
NSMutableArray *contactsArray = [NSMutableArray arrayWithCapacity:nPeople];
for (CFIndex i = 0; i < nPeople; ++i) {
ABRecordRef person = CFArrayGetValueAtIndex(allPeople, i);
if (!person) continue;
ContactData *contact = [ContactData new];
contact.firstName = (__bridge_transfer NSString*)ABRecordCopyValue(person, kABPersonFirstNameProperty);
It is working on my iPhone 5s and simulator, but the build is crashing on the tester iPod device on the line with SIGSEGV:
contact.firstName = (__bridge_transfer NSString*)ABRecordCopyValue(person, kABPersonFirstNameProperty);
Here is the stack:
3 AppSupport 0x34129a04 CPRecordCopyProperty
4 AppSupport 0x34129a04 CPRecordCopyProperty
5 AddressBook 0x2fd6ad22 ABRecordCopyValueUnfiltered
6 AddressBook 0x2fd6abc6 ABRecordCopyValue
I had the same error, the problem is that:
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering(addressBook, source, kABPersonSortByFirstName);
and
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);
are giving a different number of contacts (so in your case nPeople is probably bigger than allPeople, which causes the crash). "source" doesn't seem to be giving all the contacts in the address book. Changing it to nil solved it for me. Also, to be sure I would do: nPeople = CFArrayGetCount(allPeople);
The solution is very well explained by Jokinryou Tsui in this post: ABAddressBookCopyArrayOfAllPeople and ABAddressBookGetPersonCount return different sizes
(This is my first post, so I'm not sure if I broke any rules or followed the right procedure. I hope the answer helps!)

My app doesn't show device's contacts right after the authorization check

In my app I have to access the address book, so in iOS>6.0 I have to ask permission to the user.
I do this:
ABAddressBookRef addressBook = ABAddressBookCreate();
if(floor(NSFoundationVersionNumber) >= NSFoundationVersionNumber_iOS_6_0)
{
//iOS is >= 6.0
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
{
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error)
{
if (granted)
{
[self showContacts:addressBook];
}
});
}
else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized)
{
[self showContacts:addressBook];
}
else
{
//showAccessDeniedAlert();
}
}
else
{
//iOS is < 6.0
[self showContacts:addressBook];
}
In showContacts it's all alright, but: the first time I use the app on a device, it asks me if I want to let it access the AddressBook, i press "OK" and showContactsisn't called! So I have to close the app, restart it, and it works perfectly.
This is my showContacts method:
- (void) showContacts:(ABAddressBookRef)addressBook
{
CFArrayRef allContacts = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex numberOfContacts = ABAddressBookGetPersonCount(addressBook);
for (int ii = 0; ii < numberOfContacts; ii++)
{
ABRecordRef contactRef = CFArrayGetValueAtIndex(allContacts, ii);
ABMultiValueRef *phones = ABRecordCopyValue(contactRef, kABPersonPhoneProperty);
for(CFIndex jj = 0; jj < ABMultiValueGetCount(phones); jj++)
{
CEPerson *person = [[CEPerson alloc] init];
NSString *name = (__bridge_transfer NSString *)ABRecordCopyValue(contactRef,kABPersonFirstNameProperty);
NSString *surname = (__bridge_transfer NSString *)ABRecordCopyValue(contactRef, kABPersonLastNameProperty);
CFStringRef phoneNumberRef = ABMultiValueCopyValueAtIndex(phones, jj);
NSString *phoneNumber = (__bridge NSString *)phoneNumberRef;
person.name = name;
person.surname = surname;
person.telephone = phoneNumber;
[arrContacts addObject:person];
}
}
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"surname" ascending:YES];
[arrContacts sortUsingDescriptors:[NSArray arrayWithObject:sort]];
[self selectAllContacts];
}
How can I make it work even right after the user presses "OK"?
Thank you all.
I figured it out. I simply added
[tableView reloadData];
at the end of my showContacts method.

How to simply retrieve list of Contacts in iOS7?

I need to retrieve list of Contacts in iOS.
Here is my code that is not work.
NSMutableArray *myContacts = [[NSMutableArray alloc]init];
CFErrorRef error = NULL;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
if (addressBook!=nil)
{
NSArray *allContacts = (__bridge_transfer NSArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
NSUInteger i = 0;
for (i = 0; i<[allContacts count]; i++)
{
Person *person = [[Person alloc] init];
ABRecordRef contactPerson = (__bridge ABRecordRef)allContacts[i];
NSString *firstName = (__bridge_transfer NSString*)ABRecordCopyValue(contactPerson, kABPersonFirstNameProperty);
person.firstName = firstName;
[myContacts addObject:person];
}
CFRelease(addressBook);
}
else
{
NSLog(#"Error");
}
How can i get the list of Contacts?
You need to request access to the user's address book first. Set a flag for checking whether the user allowed/denied access.
__block BOOL userDidGrantAddressBookAccess;
CFErrorRef addressBookError = NULL;
if ( ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined ||
ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized )
{
addressBook = ABAddressBookCreateWithOptions(NULL, &addressBookError);
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error){
userDidGrantAddressBookAccess = granted;
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
else
{
if ( ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusDenied ||
ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusRestricted )
{
// Display an error.
}
}
Then you can call that method you wrote to grab the contacts. Remember to check the value of userDidGrantAddressBookAccess first.

App Crash with exc_bad_access code

Let me introduce my functionality of App,I use push notification & addressbook and CoreTelephony Framework.
What I am doing in my application is , When i get Push notification, I save the number from the Payload in a Appdelegate Variable(Incoming_NO) , if there is no Such contact with this number , Ill create the new contact and save it.
When i receive the Call , the same contact name appears as i added before , Later on I am allowing the user to Edit the Contact if he want to save the contact or to delete , When he edit the contact and later when i receive the Push notification with same number I am getting the exc_bad_access to (Incoming_NO) I have enabled the Zombie and i got the breakpoint error at the same place ..
Can anyone help me what is the issue .
Received notification: {
aps = {
alert = "Please help me-+918884718240";
sound = "beep.caf";
};
}
Code :
NSString* alertValue = [[userInfo valueForKey:#"aps"] valueForKey:#"alert"];
NSRange range=[alertValue rangeOfString:#":"];
NSString *param,*msg;
NSRange range1=[alertValue rangeOfString:#":"];
if (range1.location != NSNotFound)
{
param = [alertValue substringFromIndex:range1.location + range1.length];
msg=[alertValue substringToIndex:range.location + range.length-1];
}
else
{
range1=[alertValue rangeOfString:#"-"];
if (range1.location != NSNotFound)
{
param = [alertValue substringFromIndex:range1.location + range1.length];
msg=[alertValue substringToIndex:range1.location + range1.length-1];
}
}
if(range.length!=0)
{
parts= [NSMutableArray arrayWithArray:[alertValue componentsSeparatedByString:#":"]];
}else
{
parts = [NSMutableArray arrayWithArray:[alertValue componentsSeparatedByString:#"-"]];
}
incoming_Number =[parts objectAtIndex:1];
For this Variable(incoming_Number) I get the error when i receive the Push notification after saving the contact.
I tried to change incoming_Number type to NSString and NSMutableString , still the same error , I guess what happening is I am referring to the object which is released . But in debugger I can see it has the value.
Adding Contact Code:
- (void)setContacts:(UIImage *) imgdata :(NSString *)incoming_number {
ABRecordRef person=NULL;
ABRecordRef loopingPerson=NULL;
_Bool vizzical_present=false;
CFErrorRef myError = NULL;
NSArray *allContacts;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &myError);
// ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(nil, nil);
__block BOOL accessGranted = NO;
if (ABAddressBookRequestAccessWithCompletion != NULL) { // we're on iOS 6
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
accessGranted = granted;
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
//dispatch_release(sema);
}
else { // we're on iOS 5 or older
accessGranted = YES;
}
if (accessGranted) {
// int count = (int) ABAddressBookGetPersonCount(addressBook);
allContacts = (__bridge_transfer NSArray
*)ABAddressBookCopyArrayOfAllPeople(addressBook);
for(CFIndex i = 1; i < allContacts.count; i++)
{
loopingPerson = (__bridge ABRecordRef)allContacts[i];
CFStringRef firstName;
// char *lastNameString, *firstNameString;
firstName = ABRecordCopyValue(loopingPerson, kABPersonFirstNameProperty);
if([(__bridge NSString *)(firstName) isEqualToString:#"VizziCal"]){
vizzical_present=true;
}
ABMutableMultiValueRef phoneNumbers = ABRecordCopyValue(loopingPerson, kABPersonPhoneProperty);
// NSMutableArray *numbersArray = [[NSMutableArray alloc] init];
// CFStringRef phoneNumberLabel = ABMultiValueCopyLabelAtIndex( phoneNumbers, 0 );
CFStringRef phoneNumberValue = ABMultiValueCopyValueAtIndex( phoneNumbers, 0 );
NSString* noSpaces =
[[(__bridge NSString *)phoneNumberValue componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]componentsJoinedByString:#""];
if(phoneNumberValue!=NULL){
NSString* noHypen =
[noSpaces stringByReplacingOccurrencesOfString:#"-" withString:#""];
// CFStringRef phoneNumberLocalizedLabel = ABAddressBookCopyLocalizedLabel( phoneNumberLabel );
// NSString *addPlus=[[NSString alloc]initWithFormat:#"%#",incoming_number] ;
if([incoming_number isEqual:noHypen] || ([incoming_number rangeOfString:noHypen].location!=NSNotFound)){
NSLog(#"%# and %# and %ld",incoming_number,noHypen,i);
person=loopingPerson;
break;
}
}
}
if(person!=NULL){
CFErrorRef error = nil;
CFDataRef imageData = ABPersonCopyImageData(person);
NSData* imageData1 = (__bridge NSData*)ABPersonCopyImageData(person);
UIImage *image = [UIImage imageWithData:(__bridge NSData *)(imageData)];
UIImage *image1 = [UIImage imageWithData:(NSData *)(imageData1)];
NSData *dataRef;
UIImage *mergedImage;
if(image!=NULL)
{
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
savedImagePath= [self saveImage:image1 forPerson:#"image"];
});
dataRef = UIImagePNGRepresentation(imgdata);
CFRelease(imageData);
}
else{
if(imgdata.size.height <480 && imgdata.size.width<320)
{
UIImage *image = [UIImage imageNamed:#"blank_image.png"];
mergedImage=[self mergeTwoImages:imgdata :image];
CGFloat width = imgdata.size.width;
CGFloat height = imgdata.size.height;
NSLog(#"Height:%f and Width =%f",width,height);
dataRef = UIImagePNGRepresentation(mergedImage);
}
else{
dataRef = UIImagePNGRepresentation(imgdata);
}
}
CFDataRef cfDataRef = CFDataCreate(NULL, [dataRef bytes], [dataRef length]);
if (ABPersonHasImageData(person)) {
ABPersonRemoveImageData(person, &error);
ABAddressBookSave(addressBook, &error);
}
//ABRecordSetValue(person, kABPersonFirstNameProperty, #"Don Juan", NULL);
// ABAddressBookAddRecord(addressBook, person, &error);
if (ABPersonSetImageData(person, cfDataRef, &error)) {
if (ABAddressBookHasUnsavedChanges(addressBook)) {
NSLog(#"has unsaved changes");
} else {
NSLog(#"nothing to save");
}
if (ABAddressBookSave(addressBook, &error)) {
NSLog(#"saved");
} else {
NSLog(#"not saved");
}
}
}
else{
if(!vizzical_present)
{
ABRecordRef newPerson = ABPersonCreate();
ABRecordSetValue(newPerson, kABPersonFirstNameProperty, #"VizziCal", &myError);
CFDataRef dataRef = (__bridge CFDataRef)(UIImagePNGRepresentation(imgdata));
[prefs setBool:YES forKey:#"contact-created"];
//Phone number is a list of phone number, so create a multivalue
ABMutableMultiValueRef phoneNumberMultiValue =
ABMultiValueCreateMutable(kABPersonPhoneProperty);
ABMultiValueAddValueAndLabel(phoneNumberMultiValue ,(__bridge CFTypeRef)(incoming_number),kABPersonPhoneMobileLabel, NULL);
// ...
// Set other properties
ABRecordSetValue(newPerson, kABPersonPhoneProperty, phoneNumberMultiValue, &myError);
// ...
ABAddressBookAddRecord(addressBook, newPerson, &myError);
ABAddressBookSave(addressBook, &myError);
if (myError != NULL)
{
CFStringRef errorDesc = CFErrorCopyDescription(myError);
NSLog(#"Contact not saved: %#", errorDesc);
CFRelease(errorDesc);
}
if (ABPersonSetImageData(newPerson, dataRef, &myError)) {
if (ABAddressBookHasUnsavedChanges(addressBook)) {
NSLog(#"has unsaved changes");
} else {
NSLog(#"nothing to save");
}
if (ABAddressBookSave(addressBook, &myError)) {
NSLog(#"saved");
} else {
NSLog(#"not saved");
}
}
CFRelease(newPerson);
CFRelease(addressBook);
CFRelease(phoneNumberMultiValue);
}
else
{
ABRecordRef newPerson = ABPersonCreate();
for(CFIndex i = 1; i < allContacts.count; i++)
{
loopingPerson = (__bridge ABRecordRef)allContacts[i];
CFStringRef firstName;
// char *lastNameString, *firstNameString;
firstName = ABRecordCopyValue(loopingPerson, kABPersonFirstNameProperty);
NSString *name=(__bridge NSString *)(firstName);
if([name isEqualToString:#"VizziCal"])
break;
}
if(loopingPerson !=NULL )
{
[prefs setBool:YES forKey:#"contact-created"];
CFDataRef dataRef = (__bridge CFDataRef)(UIImagePNGRepresentation(imgdata));
//Phone number is a list of phone number, so create a multivalue
ABMutableMultiValueRef phoneNumberMultiValue =
ABMultiValueCreateMutable(kABPersonPhoneProperty);
ABMultiValueAddValueAndLabel(phoneNumberMultiValue ,(__bridge CFTypeRef)(incoming_number),kABPersonPhoneMobileLabel, NULL);
// ...
// Set other properties
ABRecordSetValue(newPerson, kABPersonPhoneProperty, phoneNumberMultiValue, &myError);
// ...
ABAddressBookAddRecord(addressBook, newPerson, &myError);
ABAddressBookSave(addressBook, &myError);
if (myError != NULL)
{
CFStringRef errorDesc = CFErrorCopyDescription(myError);
NSLog(#"Contact not saved: %#", errorDesc);
CFRelease(errorDesc);
}
if (ABPersonSetImageData(newPerson, dataRef, &myError)) {
if (ABAddressBookHasUnsavedChanges(addressBook)) {
NSLog(#"has unsaved changes");
} else {
NSLog(#"nothing to save");
}
if (ABAddressBookSave(addressBook, &myError)) {
NSLog(#"saved");
} else {
NSLog(#"not saved");
}
}
CFRelease(newPerson);
CFRelease(addressBook);
CFRelease(phoneNumberMultiValue);
}
}
}
}
}
Here what I am doing is , I am checking whether Person Exist or not , if not I am checking for the default contact "VizziCal" if that doesnt exist then I am creating the New contact as "VizziCal".
First Rather doing all this stuff for getting Incoming No, Try Custom Payload for this.
It is simple.
Take a look Push Notification Programming Guide
you could try:
{ "alert": "Please help me", "phone": "9999999999" }
NOTE: This will created at server side.

Resources