FBLoginView: login isn't performed - facebook-ios-sdk

I'm adding FBLoginView to my ViewController < FBLoginViewDelegate >:
FBLoginView *loginview = [[FBLoginView alloc] init];
loginview.frame = CGRectOffset(loginview.frame, 5, 5);
loginview.delegate = self;
[self.view addSubview:loginview];
[loginview sizeToFit];
All the necessary fields in plist (FacebookAppID, FacebookDisplayName, URL Schemes) are all set according to the tutorial. The facebook app is also configured according to the tutorial (bundle ID is set, Facebook login is enabled).
But the login still isn't performed. When I press on "log in", I get redirected to the browser with facebook login, but when it's finished, I'm not logged in the app (loginViewFetchedUserInfo:user: isn't called, "log in" hasn't changed to "log out").
What can be the problem?

Everything worked after I implemented the following in the AppDelegate.m (taken from one of the official examples):
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState) state
error:(NSError *)error
{
switch (state) {
case FBSessionStateOpen:
if (!error) {
// We have a valid session
//NSLog(#"User session found");
[FBRequestConnection
startForMeWithCompletionHandler:^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error) {
if (!error) {
self.loggedInUserID = user.id;
self.loggedInSession = FBSession.activeSession;
}
}];
}
break;
case FBSessionStateClosed:
case FBSessionStateClosedLoginFailed:
[FBSession.activeSession closeAndClearTokenInformation];
break;
default:
break;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:FBSessionStateChangedNotification
object:session];
if (error) {
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#"Error"
message:error.localizedDescription
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
}
/*
* Opens a Facebook session and optionally shows the login UX.
*/
- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI {
return [FBSession openActiveSessionWithReadPermissions:nil
allowLoginUI:allowLoginUI
completionHandler:^(FBSession *session,
FBSessionState state,
NSError *error) {
[self sessionStateChanged:session
state:state
error:error];
}];
}
/*
*
*/
- (void) closeSession {
[FBSession.activeSession closeAndClearTokenInformation];
}
/*
* If we have a valid session at the time of openURL call, we handle
* Facebook transitions by passing the url argument to handleOpenURL
*/
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// attempt to extract a token from the url
return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
}

You need to add the following to the app delegate
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// Call FBAppCall's handleOpenURL:sourceApplication to handle Facebook app responses
BOOL wasHandled = [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
// You can add your app-specific url handling code here if needed
return wasHandled;
}

You may need to implement the
(BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url method
Be sure to call:
return [FBSession.activeSession handleOpenURL:url];
When applicable.

#Sergey: Are you want to open FBDialog on your native app or in browser? If you to want open in your native app then use "FBSessionLoginBehaviorForcingWebView". Here is my code that I am using:
NSArray *permission = [NSArray arrayWithObjects:kFBEmailPermission,kFBUserPhotosPermission, nil];
FBSession *session = [[FBSession alloc] initWithPermissions:permission];
[FBSession setActiveSession: [[FBSession alloc] initWithPermissions:permission] ];
[[FBSession activeSession] openWithBehavior:FBSessionLoginBehaviorForcingWebView completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
switch (status) {
case FBSessionStateOpen:
[self yourmethod];
break;
case FBSessionStateClosedLoginFailed: {
// prefer to keep decls near to their use
// unpack the error code and reason in order to compute cancel bool
NSString *errorCode = [[error userInfo] objectForKey:FBErrorLoginFailedOriginalErrorCode];
NSString *errorReason = [[error userInfo] objectForKey:FBErrorLoginFailedReason];
BOOL userDidCancel = !errorCode && (!errorReason || [errorReason isEqualToString:FBErrorLoginFailedReasonInlineCancelledValue]);
if(error.code == 2) {
UIAlertView *errorMessage = [[UIAlertView alloc] initWithTitle:#"FBAlertTitle"
message:#"FBAuthenticationErrorMessage"
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[errorMessage performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:YES];
errorMessage = nil;
}
}
break;
// presently extension, log-out and invalidation are being implemented in the Facebook class
default:
break; // so we do nothing in response to those state transitions
}
}];
permission = nil;
or you want to open in browser then use following :
In your .h file
#import <FacebookSDK/FacebookSDK.h> and add FBLoginViewDelegate delegate
In you .m file
FBLoginView *loginview = [[FBLoginView alloc] init];
loginview.frame = CGRectOffset(loginview.frame, 5, 5);
loginview.delegate = self;
[self.view addSubview:loginview];
[loginview sizeToFit];
// use following delegate methods
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView {
// first get the buttons set for login mode
}
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
user:(id<FBGraphUser>)user {
// here we use helper properties of FBGraphUser to dot-through to first_name and
// id properties of the json response from the server; alternatively we could use
// NSDictionary methods such as objectForKey to get values from the my json object
NSLog(#"userprofile:%#",user);
}
- (void)loginViewShowingLoggedOutUser:(FBLoginView *)loginView {
//BOOL canShareAnyhow = [FBNativeDialogs canPresentShareDialogWithSession:nil];
}
- (void)loginView:(FBLoginView *)loginView handleError:(NSError *)error {
// see https://developers.facebook.com/docs/reference/api/errors/ for general guidance on error handling for Facebook API
// our policy here is to let the login view handle errors, but to log the results
NSLog(#"FBLoginView encountered an error=%#", error);
}

Related

Siri : IntentHandler not behaving correctly with device Touch ID

What is the right approach to handle device LocalAuthentication with NSIntent? Currently it is not showing payment sent pop up in Siri INSendPaymentIntent.
- (void)handleSendPayment:(INSendPaymentIntent *)intent
completion:(void (^)(INSendPaymentIntentResponse *response))completion {
__block INSendPaymentIntentResponse *siriResponse;
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
// Check if device supports TouchID
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
// TouchID supported, show it to user
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:#"Unlock using Touch ID to transaction”
reply:^(BOOL success, NSError *error) {
if (success) {
// This action has to be on main thread and must be synchronous
dispatch_async(dispatch_get_main_queue(), ^{
// Payment handling code goes here
});
}
else if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
siriResponse = [[INSendPaymentIntentResponse alloc] initWithCode:INSendPaymentIntentResponseCodeSuccess userActivity:nil];
});
}
}];
}
}
If I remove Local Authentication payment works just fine!!

create a InApp browser implementation in iOS app

I have a requirement were in i have to create a view to display webpages with back, forward and refresh button in iOS app.
how to implement this functionality?
Use UIWebView.
Set the delegate to the view controller.
self.webView.delegate = self;
Other remarkable properties,
self.webView.scalesPageToFit = YES;
To load a URLRequest,
[self.webView loadRequest:theRequest];
or for only strings, use
[self.webView loadHTMLString:htmlString baseURL:nil];
And some delegates here. web view has its own history, you can use it by calling its back and forward methods.
#pragma mark - UIWebView delegate
// You can handle your own url scheme or let the web view handle it.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(#"url=%#, %#, %#",
request.URL, request.URL.query, request.URL.host);
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
if ([request.URL.scheme compare:#"customescheme"] == NSOrderedSame) {
if ([request.URL.host compare:kSomethingDotCom] == NSOrderedSame) {
[self mymethod];
} else if ([request.URL.host compare:kAnotherDotCom] == NSOrderedSame) {
[self method2];
} else {
NSLog(#"Unsupported service.");
}
return NO;
}
}
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[_activityIndicator stopAnimating];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
[_activityIndicator stopAnimating];
[Resources showAlert:#"Could not load." withTitle:#"Error!"];
NSLog(#"%#", [error localizedDescription]);
}
#pragma mark - Actions
- (IBAction)didPressBackButton:(id)sender
{
[_webView goBack];
}
- (IBAction)didPressForwardButton:(id)sender
{
[_webView goForward];
}
Similarly you can have the stop method. To refresh reload the request again. Before going back or forward you can check the methods canGoBack or canGoForward.
See docs at, http://developer.apple.com/library/ios/#documentation/uikit/reference/UIWebView_Class/Reference/Reference.html
It is a piece of cake, all you need is UIWebview component and its delegates and you need to create a custom navigation bar that is it.

rapid persistent store for database from UIManagedDocument

I created a database through a UIManagedDocument. If I add records slowly, everything works. If I rapidly (3-4 seconds between records), the records are out of order when fetched and may not survive across the app stopping/starting.
The UIManagedDocument is created in viewDidAppear and modified in "useDocument" (code provided below) which checks for non-existant database and creates it if necessary or just opens it.
Again, I think my issue is that the core data is not immediately stored to the SQLite.
-(void)setExampleDatabase:(UIManagedDocument *)exampleDatabase {
if ( _exampleDatabase != exampleDatabase ) {
_exampleDatabase = exampleDatabase;
NSMutableDictionary *pragmaOptions = [NSMutableDictionary dictionary];
[pragmaOptions setObject:#"FULL" forKey:#"synchronous"];
[pragmaOptions setObject:#"1" forKey:#"fullfsync"];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
pragmaOptions, NSSQLitePragmasOption,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES, NSInferMappingModelAutomaticallyOption, nil];
exampleDatabase.persistentStoreOptions = options;
//
// Does the file exist?? Is not, create it.
if ( ! [[NSFileManager defaultManager] fileExistsAtPath: [self.exampleDatabase.fileURL path]] ) {
[self.exampleDatabase saveToURL:self.exampleDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
//
// OK, the creation has been done, enable the add button so records can be created
self.addButton.enabled = YES;
}];
//
// The file exists, but it's close, so open it.
} else if ( self.exampleDatabase.documentState == UIDocumentStateClosed ) {
[self.exampleDatabase openWithCompletionHandler:^(BOOL success) {
if ( success ) {
//
// It's now successfully opened, enable the add button, load the data from
// the core data database and cause the objects to be displayed in the table view
// "getCounts" is immediately below this method
self.addButton.enabled = YES;
[self getCounts:self.exampleDatabase.managedObjectContext];
[self.tableView reloadData];
} else {
NSLog(#"Error opening a closed database");
}
} ];
} else if ( self.exampleDatabase.documentState == UIDocumentStateNormal ) {
//
// OK, the database is opened. Enable the add button, load the data from
// the core data database and cause the objects to be displayed in the table view
// "getCounts" is immediately below this method
self.addButton.enabled = YES;
[self getCounts:self.exampleDatabase.managedObjectContext];
[self.tableView reloadData];
} else {
NSLog(#"Something is wrong in useDocument - it exists in an unknown state");
exit(1);
}
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
if ( ! self.exampleDatabase ) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"DefaultDb"];
self.exampleDatabase = [[UIManagedDocument alloc] initWithFileURL:url];
}
}

UIWebView resource loading with Basic Authentication

For a (web)application I am creating, I need to use Basic Authentication to load pages in my UIWebView.
Now to set the authorization header I use:
NSString *result = [NSString stringWithFormat:#"%#:%#", username, password];
NSData *resultData = [result dataUsingEncoding:NSASCIIStringEncoding];
NSString *result64 = [NSString stringWithFormat:#"Basic %#", [resultData base64Encoding]];
[request setValue:result64 forHTTPHeaderField:#"Authorization"];
In my apache access logs I can see the login is a success. But then the UIWebView wants to load the resources like style.css and jquery.min.js but requests to these resources fail because they have no Authorization header set. How can I fix this?
The solution was to subclass NSURLProtocol to authenticate the resource loading:
#import <Foundation/Foundation.h>
#import "AuthenticationUtils.h"
#interface CustomURLProtocol : NSURLProtocol <NSURLConnectionDelegate>
{
NSMutableURLRequest *_customRequest;
NSURLConnection *_connection;
}
#end
#implementation CustomURLProtocol
static NSString *AUTHORIZED_REQUEST_HEADER = #"X-AUTHORIZED";
+(BOOL) canInitWithRequest:(NSMutableURLRequest *)request
{
// check if the request is one you want to authorize
BOOL canInit = (![request.URL.scheme isEqualToString:#"file"] && [request valueForHTTPHeaderField:[AUTHORIZED_REQUEST_HEADER stringByAppendingString:[request.URL absoluteString]]] == nil);
return canInit;
}
-(id) initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client
{
_customRequest = [request mutableCopy];
[_customRequest setValue:#"" forHTTPHeaderField:[AUTHORIZED_REQUEST_HEADER stringByAppendingString:[request.URL absoluteString]]];
self = [super initWithRequest:_customRequest cachedResponse:cachedResponse client:client];
return self;
}
+(NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
{
NSMutableURLRequest *customRequest = [request mutableCopy];
[customRequest setValue:#"" forHTTPHeaderField:[AUTHORIZED_REQUEST_HEADER stringByAppendingString:[request.URL absoluteString]]];
NSString *basicAuthentication = [AuthenticationUtils getBasicAuthentication];
[customRequest setValue:basicAuthentication forHTTPHeaderField:#"Authorization"];
return customRequest;
}
- (void) startLoading
{
NSString *basicAuthentication = [AuthenticationUtils getBasicAuthentication];
[_customRequest setValue:basicAuthentication forHTTPHeaderField:#"Authorization"];
_connection = [NSURLConnection connectionWithRequest:_customRequest delegate:self];
}
- (void) stopLoading
{
[_connection cancel];
}
#pragma mark - NSURLConnectionDelegate
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// This protocol forgets to store cookies, so do it manually
if([redirectResponse isKindOfClass:[NSHTTPURLResponse class]])
{
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:[(NSHTTPURLResponse*)redirectResponse allHeaderFields] forURL:[redirectResponse URL]] forURL:[redirectResponse URL] mainDocumentURL:[request mainDocumentURL]];
}
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[[self client] URLProtocol:self didLoadData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[[self client] URLProtocol:self didFailWithError:error];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[[self client] URLProtocolDidFinishLoading:self];
}
-(NSURLRequest *) connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
{
// This protocol forgets to store cookies, so do it manually
if([redirectResponse isKindOfClass:[NSHTTPURLResponse class]])
{
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:[(NSHTTPURLResponse*)redirectResponse allHeaderFields] forURL:[redirectResponse URL]] forURL:[redirectResponse URL] mainDocumentURL:[request mainDocumentURL]];
}
// copy all headers to the new request
NSMutableURLRequest *redirect = [request mutableCopy];
for (NSString *header in [request allHTTPHeaderFields])
{
[redirect setValue:[[request allHTTPHeaderFields] objectForKey:header] forHTTPHeaderField:header];
}
return redirect;
}
#end
And in your AppDelegate add this:
[NSURLProtocol registerClass:[CustomURLProtocol class]];

UIManagedDocument - How to deal with UIDocumentStateSavingError?

I am working on my first iCloud App. After working for a while the app cannot access a UIManagedDocument any more due to an "UIDocumentStateSavingError". Is there any way to actually find out what error occurred?
This is my code to create the UIManagedDocument:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
iCloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (iCloudURL == nil) {
dispatch_async(dispatch_get_main_queue(), ^{
[self iCloudNotAvailable];
});
return;
}
iCloudDocumentsURL = [iCloudURL URLByAppendingPathComponent:#"Documents"];
iCloudCoreDataLogFilesURL = [iCloudURL URLByAppendingPathComponent:#"TransactionLogs"];
NSURL *url = [iCloudDocumentsURL URLByAppendingPathComponent:#"CloudDatabase"];
iCloudDatabaseDocument = [[UIManagedDocument alloc] initWithFileURL:url];
NSMutableDictionary *options = [NSMutableDictionary dictionary];
NSString *name = [iCloudDatabaseDocument.fileURL lastPathComponent];
[options setObject:name forKey:NSPersistentStoreUbiquitousContentNameKey];
[options setObject:iCloudCoreDataLogFilesURL forKey:NSPersistentStoreUbiquitousContentURLKey];
iCloudDatabaseDocument.persistentStoreOptions = options;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentContentsChanged:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:iCloudDatabaseDocument.managedObjectContext.persistentStoreCoordinator];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentStateChanged:) name:UIDocumentStateChangedNotification object:iCloudDatabaseDocument];
if ([[NSFileManager defaultManager] fileExistsAtPath:[iCloudDatabaseDocument.fileURL path]]) {
// This is true, the document exists.
if (iCloudDatabaseDocument.documentState == UIDocumentStateClosed) {
[iCloudDatabaseDocument openWithCompletionHandler:^(BOOL success) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
[self documentConnectionIsReady];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self connectionError:iCloudConnectionErrorFailedToOpen];
});
}
}];
} else if (iCloudDatabaseDocument.documentState == UIDocumentStateNormal) {
...
}
} else {
...
}
});
The Document already exists and thus openWithCompletionHandler: is called on the document. This fails and the UIDocumentStateChangedNotification is fired which shows a document states of 5:
UIDocumentStateClosed and
UIDocumentStateSavingError
After this the completion block gets called. What is correct way to proceed from here? Is there any way to find out what went wrong and what kind of error occurred?
I tried to re-open the document in the completion block but the result is the same.
I guess I could solve the problem by just deleting the file and recreate it. But this is obviously not an option once the app will be out in the store. I would like to know what is going wrong and give the user an appropriator way to handle the problem.
I already checked other questions here handling the UIDocumentStateSavingError (there a not a lot of them) but the seem not to be applicable for the problem here.
Any idea how I can find out what the problem is? I cannot belive that the API tells you "Something went wrong during saving but I will not tell you what!"
You can query the documentState in the completion handler. Unfortunately, if you want the exact error, the only way to get it is to subclass and override handleError:userInteractionPermitted:
Maybe something like this would help (typed freehand without compiler)...
#interface MyManagedDocument : UIManagedDocument
- (void)handleError:(NSError *)error
userInteractionPermitted:(BOOL)userInteractionPermitted;
#property (nonatomic, strong) NSError *lastError;
#end
#implementation MyManagedDocument
#synthesize lastError = _lastError;
- (void)handleError:(NSError *)error
userInteractionPermitted:(BOOL)userInteractionPermitted
{
self.lastError = error;
[super handleError:error
userInteractionPermitted:userInteractionPermitted];
}
#end
Then in you can create it like this...
iCloudDatabaseDocument = [[UIManagedDocument alloc] initWithFileURL:url];
and use it in the completion handler like this...
[iCloudDatabaseDocument openWithCompletionHandler:^(BOOL success) {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^{
[self documentConnectionIsReady];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self connectionError:iCloudConnectionErrorFailedToOpen
withError:iCloudDatabaseDocument.lastError];
});
}
}];
Based on #JodyHagins excellent snippet, I have made a UIDocument subclass.
#interface SSDocument : UIDocument
- (void)openWithSuccess:(void (^)())successBlock
failureBlock:(void (^)(NSError *error))failureBlock;
#end
#interface SSDocument ()
#property (nonatomic, strong) NSError *lastError;
#end
#implementation SSDocument
- (void)handleError:(NSError *)error userInteractionPermitted:(BOOL)userInteractionPermitted {
self.lastError = error;
[super handleError:error userInteractionPermitted:userInteractionPermitted];
}
- (void)clearLastError {
self.lastError = nil;
}
- (void)openWithSuccess:(void (^)())successBlock failureBlock:(void (^)(NSError *error))failureBlock {
NSParameterAssert(successBlock);
NSParameterAssert(failureBlock);
[self clearLastError];
[self openWithCompletionHandler:^(BOOL success) {
if (success) {
successBlock();
} else {
NSError *error = self.lastError;
[self clearLastError];
failureBlock(error);
}
}];
}
#end

Resources