Search code examples
iphoneiosfacebook-ios-sdkfacebook-login

Facebook ios sdk login from UINavigationBar


I am building an app that has a navigationitem in the navigation bar.

I am trying to understand how to use the facebook sdk to connect to facebook (authenticating) when the button is clicked.

This is not some special viewcontroller or something.

I have seen this: http://developers.facebook.com/docs/tutorials/ios-sdk-tutorial/authenticate/

but in there i need to create some things in the delegate (like a UINavigationController) that i can't use because i am using a UITabBarController..

how can i implement facebook login and session creation just from pushing the UINavigationItem?

Here is my AppDelegate.h:

#import <UIKit/UIKit.h>
#import <FacebookSDK/FacebookSDK.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UITabBarController *tbc;

@property (strong, nonatomic) FBSession *session;


@end

And my AppDelegate.m:

#import "AppDelegate.h"
#import "StatusView.h"
#import "JokesView.h"
#import "HomeView.h"
#import "TopTenView.h"
#import "UploadView.h"

@implementation AppDelegate

@synthesize tbc;
@synthesize window = _window;
@synthesize session = _session;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];

    UINavigationController*nav1 = [[UINavigationController alloc]init];
    UINavigationController*nav2 = [[UINavigationController alloc]init];
    UINavigationController*nav3 = [[UINavigationController alloc]init];
    UINavigationController*nav4 = [[UINavigationController alloc]init];
    UINavigationController*nav5 = [[UINavigationController alloc]init];

    StatusView*page1 = [[StatusView alloc]initWithNibName:@"StatusView" bundle:nil];
    JokesView*page2 = [[JokesView alloc]initWithNibName:@"JokesView" bundle:nil];
    HomeView*page3 = [[HomeView alloc]initWithNibName:@"HomeView" bundle:nil];
    TopTenView*page4 = [[TopTenView alloc]initWithNibName:@"TopTenView" bundle:nil];
    UploadView*page5 = [[UploadView alloc]initWithNibName:@"UploadView" bundle:nil];

    page1.title = @"סטטוסים";
    page2.title = @"תמונות";
    page3.title = @"ראשי";
    page4.title = @"Top 10";
    page5.title = @"העלאה";

    UITabBarItem *tab1 = [[UITabBarItem alloc] initWithTitle:@"Status"
                                                       image:[UIImage imageNamed:@"tbc-status.png"] tag:1];
    [nav1 setTabBarItem:tab1];

    UITabBarItem *tab2 = [[UITabBarItem alloc] initWithTitle:@"Jokes"
                                                       image:[UIImage imageNamed:@"tbc-jokes.png"] tag:1];
    [nav2 setTabBarItem:tab2];

    UITabBarItem *tab3 = [[UITabBarItem alloc] initWithTitle:@"Home"
                                                       image:[UIImage imageNamed:@"tbc-home.png"] tag:1];
    [nav3 setTabBarItem:tab3];

    UITabBarItem *tab4 = [[UITabBarItem alloc] initWithTitle:@"Tpp10"
                                                       image:[UIImage imageNamed:@"tbc-topten.png"] tag:1];
    [nav4 setTabBarItem:tab4];

    UITabBarItem *tab5 = [[UITabBarItem alloc] initWithTitle:@"Upload"
                                                       image:[UIImage imageNamed:@"tbc-upload.png"] tag:1];
    [nav5 setTabBarItem:tab5];


    [nav1 pushViewController:page1 animated:NO];
    [nav2 pushViewController:page2 animated:NO];
    [nav3 pushViewController:page3 animated:NO];
    [nav4 pushViewController:page4 animated:NO];
    [nav5 pushViewController:page5 animated:NO];

    tbc = [[UITabBarController alloc]init];
    tbc.viewControllers = [NSArray arrayWithObjects:nav5,nav4,nav3,nav2,nav1, nil];
    tbc.selectedIndex = 2;

    // NavBar Design
    UIImage *navbarPortrait = [[UIImage imageNamed:@"topbar.jpg"]
                               resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
    UIImage *navbarLandscape = [[UIImage imageNamed:@"topbar.jpg"]
                                resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];

    [[UINavigationBar appearance] setBackgroundImage:navbarPortrait
                                       forBarMetrics:UIBarMetricsDefault];
    [[UINavigationBar appearance] setBackgroundImage:navbarLandscape
                                       forBarMetrics:UIBarMetricsLandscapePhone];
    // NavBar Design End

    // TabBar Design
    UIImage *tabBackground = [[UIImage imageNamed:@"tbcb3ack.png"]
                              resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
    [[UITabBar appearance] setBackgroundImage:tabBackground];
    [[tbc tabBar] setBackgroundImage:tabBackground];
    // TabBar Design End

    [self.window addSubview:tbc.view];
    self.window.rootViewController = self.tbc;

    // Push Notifications
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeNone)];
    // Push Notifications End

    // Facebook Code Start

    //UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Not Logged in"
    //                                                message:@"You Log in to use all the fearues in this app"
    //                                               delegate:nil
    //                                      cancelButtonTitle:@"OK"
    //                                      otherButtonTitles:nil];

    if (FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded) {
        // To-do, show logged in view
    } else {
        //[alert show];

    }

    // Facebook Code End

    [self.window makeKeyAndVisible];
    return YES;

}

// Facebook sdk code Start

- (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
                        withSession:self.session];
}

// Facebook sdk code End

- (void)applicationWillResignActive:(UIApplication *)application
{
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
        [FBAppCall handleDidBecomeActiveWithSession:self.session];
}
#pragma mark Template generated code

@end

These are after i used the facebook ios authentication tutorial, before the actual fb session changes and login button.


Solution

  • There really are two parts to solving this problem. You first need to define a UIButton to be a UINavigationItem which will do your login (or log out) on click. And second, your app has to be ready to accept the login event. I'm not sure which part is causing you more trouble, but here's how you set up part one, the button:

    UIButton *loginButton = [UIButton buttonWithType:UIButtonTypeCustom];
    loginButton.frame = CGRectMake(0.0f, 0.0f, 75.0f, 44.0f);
    [loginButton setTitle:@"Login" forState:UIControlStateNormal];
    [loginButton addEventHandler:^(id sender)
    {
        [FBSession openActiveSessionWithReadPermissions:nil
                                           allowLoginUI:YES
                                      completionHandler:
                                           ^(FBSession *session, 
                                             FBSessionState state, NSError *error) {
                      [self sessionStateChanged:session state:state error:error];
        }];
    }
    forControlEvents:UIControlEventTouchUpInside];
    [self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:loginButton];
    

    As for part two, you need to define the sessionStateChanged function in this view controller. To save you trouble, here's the general structure that you need for the sessionStateChanged method:

    - (void)sessionStateChanged:(FBSession *)session 
                          state:(FBSessionState) state
                          error:(NSError *)error
    {
        switch (state) {
            case FBSessionStateOpen:
                // Connected to facebook... so go to your next view controller
                break;
            case FBSessionStateClosed:
            case FBSessionStateClosedLoginFailed:
                // Login failed
                [FBSession.activeSession closeAndClearTokenInformation];
                break;
            default:
                break;
        }
    
        if (error) {
            UIAlertView *alertView = [[UIAlertView alloc]
                         initWithTitle:@"Error"
                               message:error.localizedDescription
                              delegate:nil
                     cancelButtonTitle:@"OK"
                     otherButtonTitles:nil];
            [alertView show];
        }    
    }
    

    You'll also need to implement a lot of other things, especially in your app delegate. I'm happy to write out more code samples for you, but help me understand what you want to see next.

    UPDATE

    Ok, so now that I understand you want to have this facebook button stay with you on the nav bar, I would subclass the nav controller, set the subclasss to be its own delegate, and add the facebook button to each view controller that's added. The thing to note is, the navigation bar that's drawn uses the navigationItem of each view controller that's added to the navigation controller. So technically, you need to add this same button to every view controller that's pushed or popped in and out of the navigation controller. Instead of copy-pasting code around or even setting up a utility class (or super class) that all your view controllers use to insert this facebook button, a quick and dirty way is to create a single button in a navigation controller subclass and insert that same button as each view controller is shown. So here's a very bare bones version of what the navigation controller subclass might look like this in its implementation file:

    @interface MYNavigationController () <UINavigationControllerDelegate>
    @property (nonatomic, strong)           UIButton *facebookButton;
    @end
    
    @implementation MYNavigationController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        // Set this subclass as its own delegate to be able receive the willShowViewController: method
        self.delegate                       = self;
    
        // Create a shared facebook button
        _facebookButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _facebookButton.frame = CGRectMake(0.0f, 0.0f, 75.0f, 44.0f);
        [_facebookButton setTitle:@"Login" forState:UIControlStateNormal];
        [_facebookButton
            addTarget:self
            action:@selector(onFacebookButtonClick:)
            forControlEvents:UIControlEventTouchUpInside];
    }
    
    - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
    {
        // As each view controller is about to be shown, change the view controller's
        // navigationItem to have this facebook button as its right bar button
        viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.facebookButton];
    }
    
    - (void)onFacebookButtonClick:(id)sender
    {
        // Handle the click event when the facebook button is clicked
        // You need to have logic here to know if the session is open or not
        // Of course, when the session isn't open, then open a new session (ie. login)
        // and when the session IS open, then close the session (ie. logout)
        [FBSession
            openActiveSessionWithReadPermissions:nil
            allowLoginUI:YES
            completionHandler: ^(FBSession *session, FBSessionState state, NSError *error)
            {
                [self sessionStateChanged:session state:state error:error];
            }];
    }
    
    - (void)sessionStateChanged:(FBSession *)session state:(FBSessionState)state error:(NSError *)error
    {
        switch (state) {
            case FBSessionStateOpen:
                // Connected to facebook so...
                // 1. change the button text to say "logout" instead of "login"
                // 2. go to your next view controller
                break;
            case FBSessionStateClosed:
            case FBSessionStateClosedLoginFailed:
                // Login failed so revert everything to pre-login state
                [FBSession.activeSession closeAndClearTokenInformation];
                break;
            default:
                break;
        }
    
        if (error) {
            UIAlertView *alertView = [[UIAlertView alloc]
                                      initWithTitle:@"Error"
                                      message:error.localizedDescription
                                      delegate:nil
                                      cancelButtonTitle:@"OK"
                                      otherButtonTitles:nil];
            [alertView show];
        }
    }
    

    And then, simply use this MYNavigationController as your navigation controller class. Don't forget to go through the facebook ios authentication tutorial for a guided tour of how to set up your app (there's plenty of frameworks to add, infoplist changes, etc).

    Happy to help more if you need it!

    Update #2

    Because your window's root view controller is a UITabBarController, then we can use that as the owner of the facebookButton, which is shared between all UINavigationControllers. At a high level, the logic hasn't changed from my previous code sample. You're creating a single button somewhere, that's being added to every view controller that's shown by your navigation controllers. In order to do this, you need to first create a UITabBarController subclass (again, mine is called MYTabBarController, but you can call it whatever you want). And it will look something like this in MYTabBarController.m:

    #import "MYTabBarController.h"
    #import <FacebookSDK/FacebookSDK.h>
    
    @implementation MYTabBarController
    
    - (UIButton *)facebookButton
    {
        if (! _facebookButton)
        {
            // Create a shared facebook button on demand
            _facebookButton = [UIButton buttonWithType:UIButtonTypeCustom];
            _facebookButton.frame = CGRectMake(0.0f, 0.0f, 75.0f, 44.0f);
            [_facebookButton setTitle:@"Login" forState:UIControlStateNormal];
            [_facebookButton
                addTarget:self
                action:@selector(onFacebookButtonClick:)
                forControlEvents:UIControlEventTouchUpInside];
        }
        return _facebookButton;
    }
    
    - (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
    {
        // As each view controller is about to be shown, change the view controller's
        // navigationItem to have this facebook button as its right bar button
        viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.facebookButton];
    }
    
    - (void)onFacebookButtonClick:(id)sender
    {
        // Handle the click event when the facebook button is clicked
        // You need to have logic here to know if the session is open or not
        // Of course, when the session isn't open, then open a new session (ie. login)
        // and when the session IS open, then close the session (ie. logout)
        [FBSession
            openActiveSessionWithReadPermissions:nil
            allowLoginUI:YES
            completionHandler: ^(FBSession *session, FBSessionState state, NSError *error)
            {
                [self sessionStateChanged:session state:state error:error];
            }];
    }
    
    - (void)sessionStateChanged:(FBSession *)session state:(FBSessionState)state error:(NSError *)error
    {
        switch (state) {
            case FBSessionStateOpen:
                // Connected to facebook so...
                // 1. change the button text to say "logout" instead of "login"
                //      eg. [self.facebookButton setTitle:@"Logout" forState:UIControlStateNormal];
                // 2. go to your next view controller
                break;
            case FBSessionStateClosed:
            case FBSessionStateClosedLoginFailed:
                // Login failed so revert everything to pre-login state
                [FBSession.activeSession closeAndClearTokenInformation];
                break;
            default:
                break;
        }
    
        if (error) {
            UIAlertView *alertView = [[UIAlertView alloc]
                                      initWithTitle:@"Error"
                                      message:error.localizedDescription
                                      delegate:nil
                                      cancelButtonTitle:@"OK"
                                      otherButtonTitles:nil];
            [alertView show];
        }
    }
    

    The corresponding MYTabBarController.h will need to have at least a button defined and the fact it conforms to the UINavigationControllerDelegate protocol:

    @interface MYTabBarController: UITabBarController<UINavigationControllerDelegate>
    
    @property (nonatomic, strong)           UIButton *facebookButton;
    

    Finally, your app delegate's application:didFinishLaunchingWithOptions: needs to make a small change. It needs to declare the tab bar as the delegate for each of your UINavigationControllers:

    // Set the navigation controller delegates to be the tabbar in order to hook into the willShowViewController: method
    nav1.delegate                       = tbc;
    nav2.delegate                       = tbc;
    nav3.delegate                       = tbc;
    nav4.delegate                       = tbc;
    nav5.delegate                       = tbc;
    

    That should do it. Hopefully you can take it from here. Good luck!