Facebook loginViewFetchedUserInfo is called twice - iphone

I am using facebook SDK 3.0 in my app. The delegate method is called twice when after logging to facebook.
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
user:(id<FBGraphUser>)user {
//loginThroughFb=TRUE;
NSString *userId=[[NSString alloc] initWithString:[user id]];
[self soapCallForLogin:#"" password:#"" deviceId:#"" fbid:userId];
NSLog(#"%#",userId);
[userId release];
}

I tried 'HelloFacebookSample' project and the method is called only once.
So I guess the best solution for such case is to keep a reference to the last user object and compare it to the new object you get the next call, and if they're equal you can just ignore that call.
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user {
if (![self isUser:cachedUser equalToUser:user]) {
cachedUser = user;
/// Do something
}
}
- (BOOL)isUser:(id<FBGraphUser>)firstUser equalToUser:(id<FBGraphUser>)secondUser {
return
[firstUser.objectID isEqual:secondUser.objectID] &&
[firstUser.name isEqual:secondUser.name] &&
[firstUser.first_name isEqual:secondUser.first_name] &&
[firstUser.middle_name isEqual:secondUser.middle_name] &&
[firstUser.last_name isEqual:secondUser.last_name] &&
...
}

I also had this problem. I managed to fix it with an ugly hack, but it works. I keep a counter in the FBLoginView delegate. When the fetchedUserInfo is called, I check the counter. If it is greater than zero, return. Otherwise, do two things -
1. increment the message counter
2. Fire a delayed event that zeroes the message counter again.
So your fetchedUserInfo method will look like this:
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
user:(id<FBGraphUser>)user {
if ([self messageCounter] >0)
return;
else
{
self.messageCounter++;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
[self setMessageCounter:0];
});}
// Do whatever you were going to do }

Fixed in FB SDK 3.8 released on Sept 18 2013. The delegate methods are now called once per login regardless of how many times the repeated logging out and back in occur.
I was also able to reproduce this on FB SDK 3.7.1 and within their own sample program "Scrumptious"
As mentioned (at least for me) this only happens after:
Logging in once
Logging out
Logging back in (Now it happens)
What is interesting is the order of calls on re-logins:
On the first login I the calls I see are:
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView;
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user;
On the 2nd (and later) logins I see:
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user;
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView;
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user;
Which gives a handy little workaround of setting a flag in the middle method like so:
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView {
// Set flag
self.isFirstLoginDone = YES;
}
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user {
// Check
if(self.isFirstLoginDone) {
// Execute code I want to run just once
NSLog(#"fetched");
}
// Don't forget to clear the flag (I guess it shouldn't matter if everything is cleaned up)
self.isFirstLoginDone = NO;
}

There could be another reason, which i jsut faced.
My situation:
ViewController A has a login (With fbloginview and its delegate set)
User chooses to register, moves to ViewController B with another fbloginview and its delegate set.
The above makes the delegate fire twice.
I have fixed this by setting delegate to nil on ViewWillDisappear in ViewController A.
-(void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
fbLoginButton.delegate=self;
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
fbLoginButton.delegate=nil;
}

I used this simple trick :
(Define an int facebookCounter in your interface)
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
user:(id<FBGraphUser>)user {
if (self.facebookCounter==0) {
self.facebookCounter++;
return;
}
//Do stuff here
}

I needed to add thread safety in this method. A simple class variable did not work. The following two options will work, depending on the use case-
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user {
//self.executedOnce = NO; in the init method of this class
#synchronized(self){
if(!self.executedOnce) {
//do something once per init of this class
self.executedOnce = YES;
}
}
//OR- This will only execute once in the lifetime of the app, thus no need for the executedOnce flag
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//do something once per lifetime of the app
});
}

just in the loginViewFetchedUserInfo method set the delegate of the loginView to nil. then it can never be called. and if you need the login again, set the delegate to the correct object.

Related

How to programmatically dismiss system dialogs like "<App> would like to access your photos"?

Is there a way to programmatically dismiss dialogs like the ones where the app wants to access photos, access contacts and access location?
I think there's a way by swizzling API methods, but I don't really know which. What is the methodology to find out which methods need to be swizzled? If swizzling is not the way then what could be another alternative?
As a note, this is not for a product, is just for testing so swizzling is a good option if it works.
1. Overriding "access to contacts" dialog
Type this code in your UIApplicationDelegate implementation (importing <AddressBook/AddressBook.h> is necessary:
ABAuthorizationStatus ABAddressBookGetAuthorizationStatus(void) {
return kABAuthorizationStatusAuthorized;
}
2. Create a swizzling method:
Type in the following method in your UIApplicationDelegate implementation, or wherever you need it, a helper class would be best. (importing <objc/runtime.h> is required:
- (void)swizzleSelector:(SEL)selector fromInstancesOfClass:(Class)clazz withBlock:(id)block {
id replaceBlock = Block_copy(block);
Method origMethod = class_getInstanceMethod(clazz, selector);
IMP origImpl = method_getImplementation(origMethod);
IMP replaceImpl = imp_implementationWithBlock(replaceBlock);
method_setImplementation(origMethod, replaceImpl);
}
- (void)swizzleSelector:(SEL)selector fromClass:(Class)clazz withBlock:(id)block {
id replaceBlock = Block_copy(block);
Method origMethod = class_getClassMethod(clazz, selector);
IMP origImpl = method_getImplementation(origMethod);
IMP replaceImpl = imp_implementationWithBlock(replaceBlock);
method_setImplementation(origMethod, replaceImpl);
}
3. Overriding "access to location" dialog:
Do the following in your UIApplicationDelegate implementation. (importing <CoreLocation/CoreLocation.h> is required):
[self swizzleSelector:#selector(startUpdatingLocation) fromInstancesOfClass:[CLLocationManager class] withBlock:^{}];
[self swizzleSelector:#selector(startMonitoringSignificantLocationChanges) fromInstancesOfClass:[CLLocationManager class] withBlock:^{}];
[self swizzleSelector:#selector(locationServicesEnabled) fromClass:[CLLocationManager class] withBlock:^{ return NO; }];
4. Overriding "access to photos" dialog:
Do the following in your UIApplicationDelegate implementation. (importing <AssetsLibrary/AssetsLibrary.h> is required):
[self swizzleSelector:#selector(authorizationStatus) fromClass:[ALAssetsLibrary class] withBlock:^{ return ALAuthorizationStatusAuthorized; }];
[self swizzleSelector:#selector(enumerateGroupsWithTypes:usingBlock:failureBlock:) fromInstancesOfClass:[ALAssetsLibrary class] withBlock:^{}];
You should be able to access an alert from anywhere in your app like this. This works for regular alerts but I have not tried it with system alerts (It is possible they don't sit in the same hierarchy as regular alerts, in which case you can't get to them at all). If it does work and you try to release an app with this, it will almost certainly get rejected.
for (UIWindow* w in [UIApplication sharedApplication].windows)
{
for (NSObject* o in w.subviews)
if ([o isKindOfClass:[UIAlertView class]])
{
// as an example, this will just dismiss/cancel the alert
[(UIAlertView*)o dismissWithClickedButtonIndex:[(UIAlertView*)o cancelButtonIndex] animated:YES];
}
}

referencing a method in an if statement

How can I check if a method is or isn't running, in an if statement? For example-
if ([(UIButton *)sender isEqual:blueButton] && **showBlueText method is running** )
{
Keep playing.
}
else if ([(UIButton *)sender isEqual:blueButton] && **showBlueText method is NOT running** )
{
Game over.
}
-(void)showBlueText
{
blueText.hidden = NO;
[self performSelector:#selector(hideText) withObject:nil afterDelay:textDelay];
[self performSelector:#selector(showGreenText) withObject:nil afterDelay:hideDelay];
}
Just to clarify, 'showBlueText' is a part of its own loop that runs independently of this if statement. I'm just trying to check if showBlueText is currently running.
You want to record state here. Make a new instance variable in this class.
// new iVar
BOOL textIsShowing;
// method
-(void)showBlueText {
textIsShowing = YES;
blueText.hidden = NO;
[self performSelector:#selector(hideText) withObject:nil afterDelay:textDelay];
}
// method
- (void)hideText {
textIsShowing = NO;
blueText.hidden = YES;
}
// button press
- (void)buttonPressed {
if (textIsShowing) {
NSLog(#"Keep playing");
} else {
NSLog(#"Game over");
}
}
Between the time you call this method, and the animation stops, don't think of it as "running". It schedules code to be executed later. Instead you want to be notified after it has finally run.
And in this case it's easiest to keep track of the state yourself. Use a new variable to track the state of things, and change it's value when that state changes.
But can't you just check if (blueText.hidden)? Yeah, you could. But it's bad practice to store state about your program in some obscure property of a random unimportant object.
Examine your state to figure out what you show. Don't examine what's showing to figure out your state.
I suggest replace Keep playing or Game over or Doing stuff with NSLog() statements. I always use it to keep a track of the changes in program if I am getting unexpected result.
So your statement may look like:
NSLog(#"Keep playing");
I hope this helps.
Just check if the text is hidden. No need to store parallel state in your controller - all that does is create the possibility that they'll be out of sync.

UIPageControl and delay in appearing

How do I make an instance of UIPageControl appear immediately? (I have set defersCurrentPageDisplay to NO.)
I have an instance of UIPageControl which is configured (number of pages, current page and then updated) when my view appears. However there is a short, fixed delay before it appears to the user. I'd like it to appear right away.
Otherwise it's working fine.
The problem is I'm performing a lengthy background process and I've inadvertently and ultimately called updateCurrentPageDisplay etc. from this secondary thread. UIKit is not thread-safe and blocks this call until it can move it to the main thread, hence the delay.
To solve this, I've subclassed UIPageControl creating "wrapper" methods that push calls to super onto the main thread. I can then safely forget about this every time I need to speak with my page controls.
For example:
- (void) updateCurrentPageDisplay
{
#synchronized(self)
{
if ([UIDevice currentDeviceSupportsGrandCentralDispatch] == YES)
{
dispatch_async(dispatch_get_main_queue(), ^{
[super updateCurrentPageDisplay];
});
}
else
{
[super performSelectorOnMainThread:#selector(updateCurrentPageDisplay)
withObject:nil
waitUntilDone:NO];
}
}
}
I have fixed the delay issue by adding the UIPageViewController delegate's willTransitionToViewControllers and setting the pageController's index there:
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers {
for (MyContentPageViewController *contentController in pendingViewControllers) {
if ([contentController isKindOfClass:[MyContentPageViewController class]]) {
NSUInteger newIndex = contentController.pageIndex;
[self.pageControl setCurrentPage:newIndex];
}
}
Then, to avoid bugs in cases where swipe is not completed, add the following delegate method:
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{
if(finished){
for (MyContentPageViewController *contentController in previousViewControllers) {
if ([contentController isKindOfClass:[MyContentPageViewController class]]) {
NSUInteger currentIndex = MIN(MAX(0, contentController.pageIndex), _allPages.count- 1);
[self.pageControl setCurrentPage:currentIndex];
}
}
} else {
for (MyContentPageViewController *contentController in previousViewControllers) {
if ([contentController isKindOfClass:[MyContentPageViewController class]]) {
NSUInteger currentIndex = MIN(MAX(0, contentController.pageIndex), _allPages.count);
[self.pageControl setCurrentPage:currentIndex];
}
}
}
}

UISwitch invokes a Restful API call which fails, would like to revert UISwitch value

My application is a VOIP telephony toolbox.
I have a series of UISwitch controls, which the user can use to change their settings, for example if they want to alter their caller id settings.
When the user changes the setting I need to make a call to the Telephony platform over its Restful API. If the Restful call fails, then I would like to reset the switch back to its previous setting. eg If the user turns caller ID on, and it fails because of a connection failure, I would like the switch to revert back to off.
I implemented this in my switchChangedValue method, however it creates a nasty loop. When a failure happens I set the UISwitch to its previous setting, but it in turn calls the switchChangedValue method again, which fails and so on looping
Here is part of my switchChangedValue method, any ideas welcome.
//Check if its a valid response from the XSI server
if ([bs getHTTPResponseCode] >= 200 && [bs getHTTPResponseCode] < 300) {
//This is the successful case
}
else
{
// I throw an alert here
//Id really like to change the UISwitch back if it goes wrong but it causes a bad loop.
if (buttonstate == false){
[switchbutton setOn:YES animated:YES];
//This invokes my switchChangedValue again
}
else if (buttonstate == true){
[switchbutton setOn:NO animated:YES];
//This invokes my switchChangedValue again
} else{
NSLog(#"Something went bad");
}
[bs release];
You might try something like this:
Declare this in your header:
BOOL _fireAPICall;
Set it to YES whenever the particular class you're in is initialized:
- (id)init {
if (self = [super init]) {
...
_fireAPICall = YES;
...
}
return self;
}
Then:
if (_fireAPICall) {
if ([bs getHTTPResponseCode] >= 200 && [bs getHTTPResponseCode] < 300) {
// success
} else {
// failure
_fireAPICall = NO;
[switchbutton setOn:!buttonstate animated:YES];
}
} else {
_fireAPICall = YES;
// handle case where switch is turned off if necessary
}
This is assuming that you're not making an API call when the user manually turns the switch off, though - is that the case?
Updated above!

UIWebView - How to identify the "last" webViewDidFinishLoad message?

The webViewDidFinishLoad message seems to be sent each time any object in the page has been loaded. Is there a way to determine that all loading of content is done?
I'm guessing that iframes cause the webViewDidStartLoad / webViewDidFinishLoad pair.
The [webView isLoading] check mentioned as an answer didn't work for me; it returned false even after the first of two webViewDidFinishLoad calls. Instead, I keep track of the loading as follows:
- (void)webViewDidStartLoad:(UIWebView *)webView {
webViewLoads_++;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
webViewLoads_--;
if (webViewLoads_ > 0) {
return;
}
…
}
- (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error {
webViewLoads_--;
}
(Note this will only work if the start / finished pairs don't come serially, but in my experience so far that hasn't happened.)
Check this one, it will definitely work for you:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if ([[webView stringByEvaluatingJavaScriptFromString:#"document.readyState"] isEqualToString:#"complete"]) {
// UIWebView object has fully loaded.
}
}
Interesting, I wouldn't have thought it would work like that. Although I'm sure there are other ways to do it (is there a way to extract the URL from the webViewDidFinishLoad message so that you can see which one is the main page finishing loading?), the main thing I can think of is using the estimatedProgress to check the progress of the page and fire off whatever you want to do when it's 100% finished loading, which is what I do in my app. Google "iphone webview estimatedprogress" and click the first link for a guide I wrote on how to do this.
Update:
Please use phopkins' answer below instead of mine! Using private APIs in your apps is a bad idea and you will probably get rejected, and his solution is the right one.
Another way to monitor load progress with less control is to observe the WebViewProgressEstimateChangedNotification, WebViewProgressFinishedNotification, and WebViewProgressStartedNotification notifications. For example, you could observe these notifications to implement a simple progress indicator in your application. You update the progress indicator by invoking the estimatedProgress method to get an estimate of the amount of content that is currently loaded.
from http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/WebKit/Classes/WebView_Class/Reference/Reference.html
You can also use this short Category I wrote that adds blocks into UIWebView and also lets you choose between default UIWebView behavior (getting notified after each object load), or the "expected" behavior (getting notified only when the page has fully loaded).
https://github.com/freak4pc/UIWebView-Blocks
I needed to capture a variable from the page which was not fully loaded yet.
This worked for me:
- (void) webViewDidFinishLoad:(UIWebView *)webView {
NSString *valueID = [webView stringByEvaluatingJavaScriptFromString:#"document.valueID;"];
if([valueID isEqualToString:#""]){
[webView reload];
return;
}
//page loaded
}
All of the options did not really work for my use case. I used phopkins example slightly modified. I found that if the HTML loaded into the webview contained an image that would be a separate request that happened serially so we have to account for that and I did that with a timer. Not the best solution but it seems to work.:
- (void)webViewActuallyFinished {
_webViewLoads--;
if (_webViewLoads > 0) {
return;
}
//Actually done loading
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
_webViewLoads++;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(webViewActuallyFinished) userInfo:nil repeats:NO];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
_webViewLoads--;
}
hi it may be a bit far back already but i hope this helps.
i just used a notification when it enters the webViewDidFinishLoad method so that i can capture one instance of the method and then i'll detect the notification and do my logic from there.
hope this helps. it does not capture the last called instance of the webViewDidFinishLoad method, but allows you to do something once it has been called and not be repeated calls to that particular method (eg. showing a button) too.
*********EDIT*********
i did a test and managed to test it out. it actually works and the method called from the notification will be called after the full page has been loaded.
alternatively, i think you can do a counter on the delegate method webViewDidStartLoad and also on webViewDidFinishLoad to make sure that they are the same before you run your codes. this though, is an ugly hack as we will never know how many times it will be called unless like me, you are loading a html feed and can add a JavaScript to check how many elements there are on the page that you are loading. i'm just sharing some of the methods i have tried to solve this. hope it helps.
feedback is encouraged. thanks!
Here's what I use to show a spinner while DOM loads, built on top of #Vinod's answer.
Note that between webViewDidStartLoad and webViewDidFinishLoad the readyState is completed from the previous request (if any), that's why the polling should begin after webViewDidFinishLoad.
readyState possible values are loading, interactive or completed (and maybe nil ?)
- (void)webViewDidStartLoad:(UIWebView *)webView {
[self spinnerOn];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[self startDOMCompletionPolling];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
[self startDOMCompletionPolling];
}
- (void)startDOMCompletionPolling {
if (self.domCompletionListener) {
[self.domCompletionListener invalidate];
}
self.domCompletionListener = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(checkDOMCompletion) userInfo:nil repeats:YES];
}
- (void)checkDOMCompletion {
NSString *readyState = [self.webView stringByEvaluatingJavaScriptFromString:#"document.readyState"];
if (readyState != nil) {
if (readyState.length > 0) {
if ([readyState isEqualToString:#"loading"]) { //keep polling
return;
}
}
}
//'completed', 'interactive', nil, others -> hide spinner
[self.domCompletionListener invalidate];
[self spinnerOff];
}
The way I do it is this:
- (void)webViewDidFinishLoad:(UIWebView *)webview {
if (webview.loading)
return;
// now really done loading code goes next
[logic]
}