I am using Xcode 7 to write an app targeted to iOS7, using Facebook iOS idk v4.1. When I execute a facebook login, [FBSDKAccessToken currentAccessToken] is still nil in the FBSDKLoginButton callback. I have tried several SO solutions for fixing this, but none has worked for me so far.
I think the problem is in my appDelegate. Here is my code:
- (BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
//for Facebook signin
if([[FBSDKApplicationDelegate sharedInstance] application:application openURL:url sourceApplication:sourceApplication annotation:annotation])
{
return YES;
}
//for Google Signin on iOS 8 and earlier
else
{
NSDictionary* options = #{UIApplicationOpenURLOptionsSourceApplicationKey:sourceApplication, UIApplicationOpenURLOptionsAnnotationKey:annotation};
return [self application:application openURL:url options:options];
}
}
//iOS 9 and later
//for Google Signin
- (BOOL) application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options
{
if ([[[NSUserDefaults standardUserDefaults] objectForKey:UDLoginType] integerValue] == ltGoogle)
{
return [[GIDSignIn sharedInstance] handleURL:url sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey] annotation:options[UIApplicationOpenURLOptionsAnnotationKey]];
}
else
{
return YES;
}
}
When my app returns from Facebook, application:openURL:options (iOSv9+) is called. That method contains no calls to the Facebook sdk. There is a call to the Facebook sdk in application:openURL:sourceApplication:annotation (iOSv8-), but I don’t know how to translate that into the iOSv9 call, as Google does. So as far as I can tell, there’s no processing of the login URL after a Facebook login. What am I doing wrong?
Thanks
Here is how you could implement for ios 9+
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options {
return [[FBSDKApplicationDelegate sharedInstance] application:app
openURL:url
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]]
|| [[GIDSignIn sharedInstance] handleURL:url
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
annotation:options[UIApplicationOpenURLOptionsSourceApplicationKey]];
}
for below ios 9
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation
] ||
[[GIDSignIn sharedInstance] handleURL:url
sourceApplication:sourceApplication
annotation:annotation];
}
Related
Need some guidance on how to get RNN to work with react-native-siri-shortcut.
Please bear with me if my post is too verbose as I'm a noob with xCode and objective C, so don't want to miss anything out.
So I've got a couple of questions/problems with trying to get the 2 libraries to work together:
1. Setting up the initialProperties with a RCTRootView
react-native-siri-shortcuts sets up the app in AppDelegate.m like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
// Check if the app launched with any shortcuts
BOOL launchedFromShortcut = [launchOptions objectForKey:#"UIApplicationLaunchOptionsUserActivityDictionaryKey"] != nil;
//Add a boolean to the initialProperties to let the app know you got the initial shortcut
NSDictionary *initialProperties = #{ #"launchedFromShortcut":#(launchedFromShortcut) };
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:#"doesthismatter"
initialProperties:initialProperties // Add the initial properties here
launchOptions:launchOptions];
...
}
In V2 of RNN, the rootView is not being used anymore, instead we just call:
[ReactNativeNavigation bootstrap:jsCodeLocation launchOptions:launchOptions];
Therefore we are not able to pass the initialProperties through to the rootView.
2. Accessing the rootView in other parts of the code
Continuing on from the set up react-native-siri-shortcut:
// This method checks for shortcuts issued to the app
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler
{
UIViewController *viewController = [self.window rootViewController];
RCTRootView *rootView = (RCTRootView*) [viewController view];
// If the initial properties say the app launched from a shortcut (see above), tell the library about it.
if ([[rootView.appProperties objectForKey:#"launchedFromShortcut"] boolValue]) {
ShortcutsModule.initialUserActivity = userActivity;
rootView.appProperties = #{ #"launchedFromShortcut":#NO };
}
[ShortcutsModule onShortcutReceivedWithUserActivity:userActivity];
return YES;
}
Since RCTRootView isn't being used to register our app, will this part of the code work?
I've already searched the repo for issues related to initialProps and reactView and the few issues that I did find, never got answered and were closed due to inactivity.
Any help would be greatly appreciated.
Actually, searching for the launchedFromShortcut property on github I realised it's not consumed in the Javascript side. Therefore it's only usage is as flag to tell wether the app was launched from a shortcut and passing it to the RNN's RNNReactRootViewCreator seems unnecessary.
Removed that necessity the integration turned out to be pretty simple, we only need to add launchedFromShortcut = [launchOptions objectForKey:UIApplicationLaunchOptionsUserActivityDictionaryKey] != nil; to didFinishLaunchingWithOptions and check it's value on continueUserActivity.
The AppDelegate.m would look as follows:
#import "AppDelegate.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <ReactNativeNavigation/ReactNativeNavigation.h>
#import <RNSiriShortcuts/RNSiriShortcuts-Swift.h>
#implementation AppDelegate
BOOL launchedFromShortcut;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
launchedFromShortcut = [launchOptions objectForKey:UIApplicationLaunchOptionsUserActivityDictionaryKey] != nil;
[ReactNativeNavigation bootstrap:jsCodeLocation launchOptions:launchOptions];
//
// Regular RNN bootstrap code omitted for brevity sake
//
return YES;
}
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler
{
if (launchedFromShortcut) {
ShortcutsModule.initialUserActivity = userActivity;
launchedFromShortcut = NO;
}
[ShortcutsModule onShortcutReceivedWithUserActivity:userActivity];
return YES;
}
#end
My facebook access token is null despite the fact that the button shows that I'm logged in. Anyone know why this would be?
From RootViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self->login_button = [[FBSDKLoginButton alloc] init];
self->login_button.center = self.view.center;
[self.view addSubview:self->login_button];
FBSDKAccessToken* access_token =[FBSDKAccessToken currentAccessToken];
NSLog(#"Access Token, %#",access_token);
}
From ApplicationDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.window setRootViewController:[[RootViewController alloc] init]];
[self.window makeKeyAndVisible];
[self.window setBackgroundColor:[UIColor purpleColor]];
[FBSDKLoginButton class];
return [[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
}
also my -ObjC linker flag is set
The FBSDKApplicationDelegate needs to be called first to resolved cached tokens. Since you are setting the root view controller immediately, that calls your viewDidLoad before the FBSDKApplicationDelegate. Instead, you can move the FBSDKApplicationDelegate up:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[FBSDKLoginButton class];
BOOL r = [[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.window setRootViewController:[[RootViewController alloc] init]];
[self.window makeKeyAndVisible];
[self.window setBackgroundColor:[UIColor purpleColor]];
return r;
}
I got same issue today, only because I missed one step:
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
BOOL handled = [[FBSDKApplicationDelegate sharedInstance] application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation];
return handled;
}
I have had an issue where I could not get the Access Token because I am calling it before return [[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];. I needed to check the access token to check whether the user is logged in or not to decide which should be my root view controller. What I did was I tried to get the access token from the cache manually in application didFinishLaunchingWithOptions by:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
FBSDKProfile *cachedProfile = [FBSDKProfile fetchCachedProfile];
[FBSDKProfile setCurrentProfile:cachedProfile];
FBSDKAccessToken *cachedToken = [[FBSDKSettings accessTokenCache] fetchAccessToken];
NSLog(#"Cached Token: %#", cachedToken.tokenString);
if (cachedToken) {
//User is logged in, do logic here.
}
return [[FBSDKApplicationDelegate sharedInstance] application:application
didFinishLaunchingWithOptions:launchOptions];
}
You can see this method by checking the implementation of - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions in FBSDKApplicationDelegate.m
UPDATE
This does not seem to work anymore. I'm not sure why. But it seems that the fetchCachedProfile and accessTokenCache methods have been made internal. I could not access these methods anymore in my code as it gives me an error.
You could build the logic around waiting for FBSDKAccessTokenDidChangeNotification notification in your ViewController's code.
In your AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// ...
// add observer BEFORE FBSDKApplicationDelegate's - application:didFinishLaunchingWithOptions: returns
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(fbAccessTokenDidChange:)
name:FBSDKAccessTokenDidChangeNotification
object:nil];
return [[FBSDKApplicationDelegate sharedInstance] application: application didFinishLaunchingWithOptions: launchOptions];
}
- (void)fbAccessTokenDidChange:(NSNotification*)notification
{
if ([notification.name isEqualToString:FBSDKAccessTokenDidChangeNotification]) {
if ([FBSDKAccessToken currentAccessToken]) {
// token is ready to be used in the app at this point
}
}
}
It happened to me too, the "Login with Facebook" button was working as expected, I was being asked for permissions, allowed them, the application:openURL:... delegate method was being called with an URL that seemed valid, nonetheless the accessToken property remained nil.
The problem in my case was the fact that somehow Shared Keychain was removed from the application entitlements, thus the Facebook SDK was not able to save the token in keychain. Enabling the capability solved the problem.
The odd thing is that this was a silent error, so it was not clear from the beginning why the access token wasn't being set...
You should use tokenString instead, like this:
FBSDKAccessToken* access_token =[FBSDKAccessToken currentAccessToken].tokenString;
NSLog(#"Access Token, %#",access_token);
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.
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);
}
Is it possible to play a track when the app has exited/gone into a background state?
For example:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSURL *trackURL = [NSURL URLWithString:#"spotify:track:489K1qunRVBm2OpS4XGLNd"];
[[SPSession sharedSession] trackForURL:trackURL callback:^(SPTrack *track) {
if (track != nil) {
[SPAsyncLoading waitUntilLoaded:track timeout:kSPAsyncLoadingDefaultTimeout then:^(NSArray *tracks, NSArray *notLoadedTracks) {
[self.playbackManager playTrack:track callback:^(NSError *error) {
if (error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Cannot Play Track"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
} else {
self.currentTrack = track;
}
}];
}];
}
}];
}
The above code was taken from the Simple Player app provided with cocoalibspotify.
Copying and pasting code like this won't get you far when backgrounding - you can't use UI elements like UIAlertView, for a start.
You need to declare your application as supporting the audio background mode as documented by Apple. Then, when you go into the background you should make a background task to start playback.