Fetching array of contacts from ABAddressBookRequestAccessWithCompletion() gives nil - ios

I am trying to print out all of my phone's contacts to the console, using NSLog(). Currently this code is just printing (null).
.h
#property (nonatomic, strong) NSMutableArray *contactsObjects;
.m
#synthesize contactsObjects;
//lazy instantiation.
- (NSMutableArray *)contactsObjects
{
if(!contactsObjects)
{
contactsObjects = [[NSMutableArray alloc]init];
}
return contactsObjects;
}
- (void)viewWillAppear:(BOOL)animated
{
CFErrorRef error = nil;
// Request authorization to Address Book
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &error);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
if (granted) {
// First time access has been granted, add all the user's contacts to array.
CFMutableArrayRef contactsObjects = ABAddressBookCopyArrayOfAllPeople(addressBookRef);
} else {
// User denied access.
// Display an alert telling user that they must allow access to proceed to the "invites" page.
}
});
}
else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
// The user has previously given access, add all the user's contacts to array.
CFMutableArrayRef contactsObjects = ABAddressBookCopyArrayOfAllPeople(addressBookRef);
}
else {
// The user has previously denied access
// Send an alert telling user that they must allow access to proceed to the "invites" page.
}
NSLog(#"%#", contactsObjects);
}
I get two warnings here:
I have no idea what I am supposed to do in order to properly print the names and numbers of my contacts to the console.
How do I print my contacts names and numbers?

You have a scope problem with your code. The contactsObjects variables in viewWillAppear: are not related to the ivar you have called contactsObjects. You're declaring new variables that are using the same name. The NSLog() at the end, on the other hand, is the ivar. But setting those other variables didn't put anything into the ivar, so you see (null), which is how NSLog() represents "no object".
Fix this by not making new variable declarations, but using the ivar.
if (granted) {
contactsObjects = ABAddressBookCopyArrayOfAllPeople(addressBookRef);
else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
contactsObjects = ABAddressBookCopyArrayOfAllPeople(addressBookRef);
You will also need to cast these:
contactsObjects = (__bridge_transfer NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBookRef);
(Also, the function doesn't return a mutable array, so you may have trouble down the road with that.)
The second problem is that ABAddressBookRequestAccessWithCompletion() doesn't stop and wait for its completion Block to run. While access is being requested, the rest of your method carries on, so you reach the NSLog() before contactsObjects is actually set in that case.

You say it prints out null and that you get an error. But this would explain your error.
contactObjects is defined within the if block and the else if block. So by the time you are outside of your conditional it's no longer defined.
Try this
- (void)viewWillAppear:(BOOL)animated
{
CFErrorRef error = nil;
// Request authorization to Address Book
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, &error);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
if (granted) {
// First time access has been granted, add all the user's contacts to array.
contactsObjects = ABAddressBookCopyArrayOfAllPeople(addressBookRef);
} else {
// User denied access.
// Display an alert telling user that they must allow access to proceed to the "invites" page.
}
});
}
else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
// The user has previously given access, add all the user's contacts to array.
contactsObjects = ABAddressBookCopyArrayOfAllPeople(addressBookRef);
}
else {
// The user has previously denied access
// Send an alert telling user that they must allow access to proceed to the "invites" page.
}
NSLog(#"%#", contactsObjects);
}

Related

XMPPFramework - How to get offline user profile status

I want to get offline user (friend) status:
My code is working fine when user is online, so i am getting user status but when he goes offline user.primaryResource.presence.status gives nil value.
XMPPUserCoreDataStorageObject *user = [[self fetchedResultsController] objectAtIndexPath:indexPath];
NSLog(#"Status: %#",user.primaryResource.presence.status);
After user goes offline i check the XMPPResourceCoreDataStorageObject table for particular jidStr its give me zero objects means when user goes offline its remove the ResourcesObject of that user.
So, there is any possibility to get a status of that user.
You check the source code of XMPPUserCoreDataStorageObject
- (void)updateWithPresence:(XMPPPresence *)presence streamBareJidStr:(NSString *)streamBareJidStr
{
XMPPResourceCoreDataStorageObject *resource =
(XMPPResourceCoreDataStorageObject *)[self resourceForJID:[presence from]];
if ([[presence type] isEqualToString:#"unavailable"] || [presence isErrorPresence])
{
if (resource)
{
[self removeResourcesObject:resource];
[[self managedObjectContext] deleteObject:resource];
}
}
else
{
if (resource)
{
[resource updateWithPresence:presence];
}
else
{
XMPPResourceCoreDataStorageObject *newResource;
newResource = [XMPPResourceCoreDataStorageObject insertInManagedObjectContext:[self managedObjectContext]
withPresence:presence
streamBareJidStr:streamBareJidStr];
[self addResourcesObject:newResource];
}
}
[self recalculatePrimaryResource];
}
You can see the resource will be removed if offline.
For myself, I check user.primaryResource?.presence?.type() == "available" as online, other cases as offline. (Assume user is the XMPPUserCoreDataStorageObject you got from db)

How to prevent _User table from growing unnecessarily when using PFAnonymousUtils iOS SDK?

What is the best way to handle the scenario described below?
I am giving the user limited access (READONLY) when logged in anonymously.
The problem i am facing is that i am afraid is that the _User table would grow unnecessarily with redundant data.
Steps:
1. user logged in anonymously
2. clear cache
3. logged in anonymously again
problem : rows in _User table is added, older user is not removed.
Code:
+ (void) ParseLoginAnonymouslyWithBlock:(LoginAnonymousBLock)completionBlock {
// if ([PFUser currentUser] && [ParseUtilities isUserAnonymous]) {
// // user already logged in anonymously.
// // prevent duplicates in table
// completionBlock([PFUser currentUser], nil);
// return;
// }
[PFAnonymousUtils logInWithBlock:^(PFUser *user, NSError *error) {
// if (error) {
// completionBlock (user, error);
// } else {
// completionBlock (user, error);
// }
completionBlock(user, error);
}];
}
In View controller:
- (IBAction)loginGuestTapped:(id)sender {
NSLog(#"Guest Login tapped");
[ParseUtilities ParseLoginAnonymouslyWithBlock:^(BOOL succeeded, NSError *error) {
if (error) {
NSLog(#"%#", &error);
} else {
NSLog(#"success");
// user logged in
NSLog(#"isanonymous: %i and %#", [ParseUtilities isUserAnonymous], [PFUser currentUser]);
}
}];
}
You could just add a 'latest_activity' flag into every user and update it when the user uses your app.
Then create a BackgroundJob (https://parse.com/docs/js/guide#cloud_code) and remove the anonymous users that are older than ~2weeks?
What you could also do is setup Push Notifications in you app and for each user that gave you a push notification token make sure the push notification does not 'reach' the recipient. In that case you can delete him since he doesn't even have the app installed anymore. (False positives are much lower in this case than the other, but you should combine them anyway!)

objective-c Completion Block issue

I am trying to get a JSON (NSString *) of the address book , in async task , however I want it to be asynchronously and with a completion block .
I have managed to retrieve that easy without the completion block , however when I am adding the completion block I have the following compiler error :
Incompatible block pointer types passing NSString *(^)(bool, CFErrorRef)' to parameter of type 'ABAddressBookRequestAccessCompletionHandler' (aka 'void (^)(bool, CFErrorRef)')
here is my code
+ (NSString *) getAddressBook:(id (^)())block {
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
if (status == kABAuthorizationStatusDenied)
{
// if you got here, user had previously denied/revoked permission for your
// app to access the contacts, and all you can do is handle this gracefully,
// perhaps telling the user that they have to go to settings to grant access
// to contacts
[[[UIAlertView alloc] initWithTitle:nil message:#"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
return nil;
}
CFErrorRef error = NULL;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
if (error)
{
NSLog(#"ABAddressBookCreateWithOptions error: %#", CFBridgingRelease(error));
if (addressBook) CFRelease(addressBook);
return nil;
}
if (status == kABAuthorizationStatusNotDetermined)
{
// present the user the UI that requests permission to contacts ...
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) -> THE ERROR OCCURS HERE !
{...
I think the problem is within my completion blocks , but I can't find some good example/tutorial for my specific issue I would be really glad if some 1 could help me please.
Your problem is that you are declaring getAddressBook like this(if we strip block)
-(id)block
And you are trying to pass it as a function of type:
-(void)block:(BOOL)aBool error:(CFErrorRef*)error
Blocks are quite hard to get used with syntax(which is improved in swift) however I'm always refering to this site when not sure:
block syntax reference
UPDATE:
You have to actually wait for your async method finish. You may also refer to official documentation
+ (void) getAddressBook:((^)(NSString* result))block {
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
if (status == kABAuthorizationStatusDenied)
{
// if you got here, user had previously denied/revoked permission for your
// app to access the contacts, and all you can do is handle this gracefully,
// perhaps telling the user that they have to go to settings to grant access
// to contacts
[[[UIAlertView alloc] initWithTitle:nil message:#"This app requires access to your contacts to function properly. Please visit to the \"Privacy\" section in the iPhone Settings app." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
block(nil);
}
CFErrorRef error = NULL;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
if (error)
{
NSLog(#"ABAddressBookCreateWithOptions error: %#", CFBridgingRelease(error));
if (addressBook) CFRelease(addressBook);
block(nil);
}
if (status == kABAuthorizationStatusNotDetermined)
{
// present the user the UI that requests permission to contacts ...
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) -> THE ERROR OCCURS HERE !
{
NSString* result = whatever
block(result);
});
}
}
And use it like this:
[YourClass getAddressBook:^(NSString* result)
{
//now you really have result
}];

Firebase observeEventType not firing after FirebaseSimpleLogin using Facebook?

I’m testing code based on the firechat-ios example. I’ve added the FirebaseSimpleLogin call loginToFacebookAppWithId and have it set up so that one view controller performs the login and then transitions to a different view controller that holds the chat logic:
self.firebase = [[Firebase alloc] initWithUrl:#"https://xxxxx.firebaseio.com/"];
[self observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"%#", snapshot.value);
// Add the chat message to the array.
[self.chat addObject:snapshot.value];
// Reload the table view so the new message will show up.
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}];
FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:self.firebase];
[authClient loginToFacebookAppWithId:kFacebookAppID permissions:#[#"email"]
audience:ACFacebookAudienceOnlyMe
withCompletionBlock:^(NSError *error, FAUser *user) {
if (error != nil) {
// There was an error logging in
NSLog(#"facebook error");
} else {
// We have a logged in facebook user
NSLog(#"facebook logged in");
[authClient checkAuthStatusWithBlock:^(NSError* error, FAUser* user) {
if (error != nil) {
// Oh no! There was an error performing the check
NSLog(#"auth error");
} else if (user == nil) {
// No user is logged in
NSLog(#"auth not logged in");
} else {
// There is a logged in user
NSLog(#"auth logged in");
// segue to the chat view controller
[self performSegueWithIdentifier:#"segueToViewController" sender:self];
}
}];
}
}];
Here are the firebase rules:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
The problem is, about 10% of the time, the UITableView of the chat messages is blank, and I don’t see any chat message entries in the log. I’ve tried playing around with the order of observeEventType, putting it before and after the loginToFacebookAppWithId call.
I’m wondering if there is a race condition where maybe the messages are arriving before I call observeEventType. I’ve checked the return value of observeEventType and I get a FirebaseHandle of 1 even when no messages arrive. I’ve also upgraded the firebase framework that comes with firechat ios to https://cdn.firebase.com/ObjC/Firebase.framework-LATEST.zip and it still fails.
I thought that maybe the connection dropped, but I’m able to post messages with childByAutoId after I’ve authenticated and see them appear on the firebase server. I just never receive any messages.
I wonder if it’s trying to send me the messages in the brief moment before I’m authenticated, and failing because I don’t have read permission. Is there a way to delay event observations until after I’m in?
I’ve tried everything I can think of but I can’t make it work reliably.
---------- UPDATE ----------
I seem to be able to log in every time if I type my credentials manually. I'm currently checking for a previous successful login with:
[FBSession openActiveSessionWithAllowLoginUI:false]
To determine if I successfully logged in on the last launch of the app. If it fails, I go to a view controller for FirebaseSimpleLogin. But if it works, I call FirebaseSimpleLogin in the current view controller and wait till it succeeds in the background.
I'm running in the simulator, so I tried deleting the preferences plist at:
~/Library/Application Support/iPhone Simulator/7.0.3/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Library/Preferences/com.xxxxxxxxxx.plist
and relaunching, which forces me to re-authenticate. Then I tried typing in my credentials and logging in 25 times without a problem.
So I think the problem is either somehow related to trying to login with Facebook before I use FirebaseSimpleLogin, or logging in with credentials from the previous launch (without bringing up the login dialog). I'm still trying to narrow down the culprit.
---------- UPDATE 2 ----------
I just wanted to add a note that after further testing, I found that the call to:
[FBSession openActiveSessionWithAllowLoginUI:false]
has no effect on FirebaseSimpleLogin. If I skip that call altogether and simply substitute true or false there, I can reproduce the issue. The problem turned out to be a race condition, see my answer below.
I finally figured out what was happening, it was due a wrong assumption on my part about UIViewController message callbacks and CFRunLoop.
The code sample in my question was distilled down from my real code to remove extraneous calls, but it turns out the part I removed was actually the culprit. I had written a function to log in and wait until success or failure on the spot (rather than receiving the response in a block later) by using a run loop:
-(bool)loginUsingFacebookReturningError:(NSError**)error andUser:(FAUser**)user
{
__block NSError *errorTemp;
__block FAUser *userTemp;
[self loginUsingFacebookWithCompletionBlock:^(NSError *error, FAUser *user) {
errorTemp = error;
userTemp = user;
CFRunLoopStop(CFRunLoopGetCurrent());
}];
CFRunLoopRun(); // needs a timeout or way for the user to cancel but I haven't implemented it yet
if(error) *error = errorTemp;
if(user) *user = userTemp;
return !errorTemp && userTemp;
}
-(void)loginUsingFacebookWithCompletionBlock:(void (^)(NSError* error, FAUser* user))block
{
FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:self.firebase];
[authClient loginToFacebookAppWithId:kFacebookAppID permissions:#[#"email"]
audience:ACFacebookAudienceOnlyMe
withCompletionBlock:^(NSError *error, FAUser *user) {
if (error != nil) {
// There was an error logging in
NSLog(#"facebook error");
block(error, nil);
} else {
// We have a logged in facebook user
NSLog(#"facebook logged in");
[authClient checkAuthStatusWithBlock:block];
}
}];
}
This was called with:
NSError *error;
FAUser *user;
bool success = [self loginUsingFacebookReturningError:&error andUser:&user];
The way loginUsingFacebookReturningError works is, it calls loginUsingFacebookWithCompletionBlock which fires off the loginToFacebookAppWithId and checkAuthStatusWithBlock messages like usual, but then I start a run loop. The run loop allows processing to happen in the background, even though the main thread pauses on CFRunLoopRun() until the completion block calls CFRunLoopStop().
What I hadn't realized is that run loops continue to process the application's messages in the background. So while I thought program flow had stopped in viewDidLoad, it had actually continued and called viewWillAppear, which is where I had placed my call to observeEventType (because I assumed that authentication would be complete by the time the program got there).
This created a race condition where the program attached the observeEventType callback during the time that Facebook and Firebase were authenticating. 90% of the time, authentication had completed before observeEventType was called, but 10% of the time there was lag or other network delays and observeEventType was called prematurely.
I fixed the problem by moving the FirebaseSimpleLogin code to its own view controller in the storyboard, and using the completion block to initiate the segue to the next view controller, which installed the observeEventType callback.
So to summarize: the solution is to call FirebaseSimpleLogin's authentication, and then AFTER it has finished and the completion block is done, call observeEventType. Otherwise Firebase's rules will deny your request to see data that's only visible to authenticated users (which is correct).
Here is the final code, untested but the method works:
// only global for illustration purposes, should really go in a singleton or AppDelegate, or be passed through the segue to the next view controller
Firebase *gFirebase;
// LoginViewController (root view controller in storyboard)
- (void)viewDidLoad
{
[super viewDidLoad];
gFirebase = [[Firebase alloc] initWithUrl:#"https://xxxxx.firebaseio.com/"];
FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:gFirebase];
[authClient loginToFacebookAppWithId:kFacebookAppID permissions:#[#"email"]
audience:ACFacebookAudienceOnlyMe
withCompletionBlock:^(NSError *error, FAUser *user) {
if (error != nil) {
// There was an error logging in
NSLog(#"facebook error");
} else {
// We have a logged in facebook user
NSLog(#"facebook logged in");
[authClient checkAuthStatusWithBlock:^(NSError* error, FAUser* user) {
if (error != nil) {
// Oh no! There was an error performing the check
NSLog(#"auth error");
} else if (user == nil) {
// No user is logged in
NSLog(#"auth not logged in");
} else {
// There is a logged in user
NSLog(#"auth logged in");
// segue to the chat view controller
[self performSegueWithIdentifier:#"segueToViewController" sender:self];
}
}];
}
}];
}
// ViewController (destination of segueToViewController)
- (void)viewDidLoad
{
[super viewDidLoad];
[gFirebase observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"%#", snapshot.value);
// Add the chat message to the array.
[self.chat addObject:snapshot.value];
// Reload the table view so the new message will show up.
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}];
}

App not asking for Contact access permission on iOS 6

I am using below Code, My application don't ask permission on iOS 6 while on iOS 7 and above version it ask for Contact permission access. On iOS 6 it doesn't show app in privacy setting as well. I have read some other thread but not found any solutions.
App crashed in iOS 6 when user changes Contacts access permissions
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"6.0")) {
__block CDNDeviceContact *controller = self;
// Request authorization to Address Book
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, NULL);
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) {
ABAddressBookRequestAccessWithCompletion(addressBookRef,
^(bool granted, CFErrorRef error) {
if (granted)
[controller loadContacts];
else [controller doAlertForContact];
});
} else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
// The user has previously given access, add the contact
[self loadContacts];
} else {
[controller doAlertForContact];
}
if (addressBookRef) CFRelease(addressBookRef);
}
If the user has previously been presented with the request to get permission, it will not show again. According to the documentation,
The user is only asked for permission the first time you request access. Later calls use the permission granted by the user.
If testing in the simulator, I recommend that you go to iOS Simulator -> Reset Content and Settings so that you are able to simulate the event.
You don't need SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO and you also don't need to create an ABAddressBookRef.
For me, this is works like a charm:
if (ABAddressBookGetAuthorizationStatus) {
switch (ABAddressBookGetAuthorizationStatus()) {
case kABAuthorizationStatusNotDetermined:{
ABAddressBookRequestAccessWithCompletion(self.addressBook, ^(bool granted, CFErrorRef error) {
self.addContactButton.enabled = granted;
if (granted) {
// granted
} else {
// User denied access
}});
} break;
case kABAuthorizationStatusDenied: break;
case kABAuthorizationStatusAuthorized: break;
default: break;
}
}

Resources