Friday, September 7, 2012

How To: Facebook Upload Photo and Update Status Using SSO - Easiest Way (2012 Facebook SDK)

Hiya. Another tutorial with sample project for noobs! YAY!

Facebook SDK for iOS. Scary isn't it. Oooooo.... brrrrrr...... You will be if you go through their iOS integration tutorials. I have never used FB SDK directly before this. I used a FB wrapper FacebookAgent from a fellow dev, Aman. But I think he no longer support updates on it. And recently Facebook updated their SDK to use a magic thingy called SSO.


Anyway if you try to implement FB SDK and read how to upload a photo from your iOS apps,  you'd be like, "WTF are all these things??!? I just want to upload a friggin photo to my wall!" Graph API? SSO? FBSession?


XCodeNoobies to the rescue! Yay!

It is pretty easy to upload, but the "setting up" can be a pain because there are quite a lot of things to do.

First, let me outline the process that FB introduced in iOS FB integration. It is called SSO. Basically, it means Single Sign On. What this means is that you only need to Sign in once in a particular device USING FACEBOOK APP OR USING SAFARI, and after that that account CAN BE USED INSTANTLY for all other apps that implements SSO. So your app needs to open FB App (or Safari on facebook.com) request to Sign On, and upon Signed On,  request a permission(s) for your app to upload a photo (or post status update or other things), and then returns back to your app and complete the upload request.

Secondly, FB SDK provides a few ways to upload photos to a wall. There is a simple photo upload method (which is going to be discussed here) and also there are others like using Graph API (which is a bit more complicated, and I won't be covering that here).

Ready?

First of all, obviously you need to download and install the Facebook SDK. At this moment, FB SDK is automatically installed into your Documents folder of your Mac when you download and run the installer. Open that folder and there is another folder named FacebookSDK.framework. Drag this folder into your project to add it (you can choose to copy to your project folder, or not, entirely up to you). Next, add also Accounts.framework from XCode's Build Phases -> Link Binary with Libraries.

NOTE: This tutorial is based on Facebook SDK 3.0. If you have different version, please check at the Facebook SDK, what are the framework requirements.


Next, open your appname-Prefix.pch file inside the Supporting Files group of XCode project and add the following line:



#import <Availability.h>

#ifndef __IPHONE_4_0
#warning "This project uses features only available in iOS SDK 4.0 and later."
#endif

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
#endif

#import <FacebookSDK/FacebookSDK.h> // -- ADD THIS

Next go to your Info.plist file and Add 2 new rows as follows. The number is your Facebook App ID (in Facebook - goto developers.facebook.com/apps.php to get this - I assume you have already added a Facebook App to integrate with your iPhone app).


Below's a screenshot of a dummy Facebook app I created for you to test in this sample project. This screenshot if from http://developers.facebook.com/ website. You need a developer account on Facebook to create new FB apps to integrate with your iPhone app. (I won't be covering this topic though).


That is the App ID to be entered in the info.plist entries above. The URL Scheme entry needs a prefix of "fb" at the front of the number. Do not miss this!


Remember to change the row's name "URL identifier" (that appears automatically) into "URL Schemes".


Now, open up the AppDelegate.m and import the FacebookSDK.

#import <FacebookSDK/FacebookSDK.h>


Then, add the following code in the implementation section:
 
- (BOOL)application:(UIApplication *)application 
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation {
    // attempt to extract a token from the url
    return [FBSession.activeSession handleOpenURL:url]; 
}



- (void)applicationWillTerminate:(UIApplication *)application
{
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    
    [FBSession.activeSession close];
}


The application:openURL:sourceApplication method is called when the app returns from another app, in this case either Facebook App or Safari App. We return the url to handleOpenURL method of the FBSession in our app.

Next, we go to the project's Target's Build Settings and add "-lsqlite3.0" in the "Other Linker Flags" row. See below for example:


Now we're pretty much done on the "Setting up" part, so lets go ahead and create User interface and apply methods to them.

To make it simple, I will show just 2 example codes -

1. to post a Status update
2. to upload a photo.


So, lets add 1 UIImageView, 1 label and 2 buttons (1 to upload the photo from that UIImageView and another to post a status from the label). We also add 1 UIActivityIndicatorView so that we can show to user when the app is doing something (busy), and set it to be hidden in IB. Here's what it will look like:




Next, declare these objects and methods (when buttons are tapped) in ViewController.h (header file) as below:

 
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController {
    IBOutlet UIImageView *myImg;
    IBOutlet UILabel *myStatus;
    IBOutlet UIActivityIndicatorView *activity;
    IBOutlet UIButton *btnUploadImg, *btnUpdateStatus;
}

@property (nonatomic, retain) UIImageView *myImg;
@property (nonatomic, retain)  UILabel *myStatus;
@property (nonatomic, retain) UIActivityIndicatorView *activity;
@property (nonatomic, retain) UIButton *btnUploadImg, *btnUpdateStatus;

-(IBAction)uploadPhoto:(id)sender;
-(IBAction)updateStatus:(id)sender;

@end


Then, synthesize them in your ViewController.m file. It is a good idea to add the dealloc immediately at this point so you won't forget about it later. The CONNECT the objects to its IBOutlets and IBActions in Interface Builder (in your XIB file).

 
@synthesize myImg, myStatus, activity, btnUploadImg, btnUpdateStatus;


-(void)dealloc {
    [super dealloc];
    [myImg release]; [myStatus release];[activity release];
    [btnUploadImg release]; [btnUpdateStatus release];
}


Lets make one more custom method to set user interface status. When we use the app, there are times where the app is going to be busy contacting Facebook server, or busy uploading or downloading data, so it is good practise to manage the objects in your screen properly. Lets do this by creating a method called controlStatusUsable:(BOOL)usable.


-(void)controlStatusUsable:(BOOL)usable {
    if (usable) {
        btnUploadImg.userInteractionEnabled = YES;
        btnUpdateStatus.userInteractionEnabled = YES;
        self.activity.hidden = YES;
        [self.activity stopAnimating];
    } else {
        btnUploadImg.userInteractionEnabled = NO;
        btnUpdateStatus.userInteractionEnabled = NO;
        self.activity.hidden = NO;
        [self.activity startAnimating];
    }
    
}


What this does is just disable the buttons, show the "busy" sign when usable= NO and enable back the buttons and remove the "busy" sign when usable = YES.

To be able to upload photo (and to update status) intelligently, we need to ask the user for permission to do so (this is a requirement by Facebook), AND we need to reconfirm with the user which FB account that we are uploading the photo to (or update status to).


To accomplish this, upon user tapping the upload button (or update status button), we need to check for FB login details from Facebook app (or Safari on facebook.com) and then request a permission from there, then return that permission back to our app, and the show the user the FB account name, and upon confirmation, we do the action (upload photo or update status).

The code to do all this is as follows (see the comments for details):


// First, check whether the Facebook Session is open or not 

    if (FBSession.activeSession.isOpen) {

        // Yes, we are open, so lets make a request for user details so we can get the user name.

        [self promptUserWithAccountName];// a custom method - see below:
        
        
        
    } else {
        
        // We don't have an active session in this app, so lets open a new
        // facebook session with the appropriate permissions!

        // Firstly, construct a permission array.
        // you can find more "permissions strings" at http://developers.facebook.com/docs/authentication/permissions/
        // In this example, we will just request a publish_stream which is required to publish status or photos.

         NSArray *permissions = [[NSArray alloc] initWithObjects:
                                @"publish_stream",
                                nil];
        
        // OPEN Session!
        [self controlStatusUsable:NO];
        [FBSession openActiveSessionWithPermissions:permissions
                                       allowLoginUI:YES
                                  completionHandler:^(FBSession *session, 
                                                      FBSessionState status, 
                                                      NSError *error) {
                                      // if login fails for any reason, we alert
                                      if (error) {
                                          
                                          // show error to user.

                                      } else if (FB_ISSESSIONOPENWITHSTATE(status)) {
                                          
                                          // no error, so we proceed with requesting user details of current facebook session.
                                          
                                         [self promptUserWithAccountName];   // a custom method - see below:                              
                                      }
                                      [self controlStatusUsable:YES];
                                  }];
    }

The method promptUserWithAccountName is a custom method we create to optimize code, because we are using the same exact lines of code twice in the uploadPhoto method. See the comments for details below:

 

-(void)promptUserWithAccountName {
[self controlStatusUsable:NO];
    [[FBRequest requestForMe] startWithCompletionHandler:
     ^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *user, NSError *error) {
         if (!error) {
             
             UIAlertView *tmp = [[UIAlertView alloc] 
                                 initWithTitle:@"Upload to FB?" 
                                 message:[NSString stringWithFormat:@"Upload to ""%@"" Account?", user.name]
                                 delegate:self 
                                 cancelButtonTitle:nil
                                 otherButtonTitles:@"No",@"Yes", nil];
             tmp.tag = 100; // We are also setting the tag to this alert so we can identify it in delegate method later
             [tmp show];
             [tmp release];
             
         }
         [self controlStatusUsable:YES]; // whether error occur or not, enable back the UI
     }];  
}

To those who are not familiar with Block Methods, the one in promptUserWithAccountname method is an example of Block Method. I think it was introduced by Apple in iOS 3.2 or 4.0, I can't remember. But it is a cool method that diminishes delegates.

So what the heck is it?

Simple. It is just a "execute this, and when you're done, do this" method.


[[class method] startUponCompletion: ^(stuffs to carry around) {
   // write in here what you want your app to do after the start method is completed.
}];


Get it right? Normally in delegates type method, we call a method, and we need to write a delegate method to do something when it is done. So block method is neater and cooler.

Talking about delegates, we are using it now in alertView. We prompt user with alertViews so we need to take those inputs and process them. Here is the alertView delegates:


-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    
    if (buttonIndex==1) { // yes answer

        // did the alert responded to is the one prompting about user name? if so, upload!
        if (alertView.tag==100) {
            // then upload
            [self controlStatusUsable:NO];

             // Here is where the UPLOADING HAPPENS!
            [FBRequestConnection startForUploadPhoto:myImg.image 
                                   completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
                                        if (!error) {
                                           UIAlertView *tmp = [[UIAlertView alloc] 
                                                               initWithTitle:@"Success" 
                                                               message:@"Photo Uploaded"
                                                               delegate:self 
                                                               cancelButtonTitle:nil
                                                               otherButtonTitles:@"Ok", nil];
                                        
                                           [tmp show];
                                           [tmp release];
                                       } else {
                                           UIAlertView *tmp = [[UIAlertView alloc] 
                                                               initWithTitle:@"Error" 
                                                               message:@"Some error happened"
                                                               delegate:self 
                                                               cancelButtonTitle:nil
                                                               otherButtonTitles:@"Ok", nil];
                                           
                                           [tmp show];
                                           [tmp release];
                                       }
                                       
                                    [self controlStatusUsable:YES];
                                   }];
            
        }
        
    }
    
}


As for posting a status, it also uses the similar process, but a little different methods. I won't be explaining about it here, but the codes are all included in the sample project. Below is the screenshots of the app in action using SSO.



Here in simulator there is no Facebook App, so Safari will launch and request you to login, and if you are already logged in, it will show the App page and user must tap Log In button.

Then Facebook will request permission to user, where user should tap the Allow All button.
Upon tapping that, Safari will go away and returns your app to the screen.

Your app "uponCompletion" is called and retrieve user info directly and show a prompt.

Do the upload and upon finish, show an alert. DONE! BOOM!

Proof that it works!

There are certain things I've left out in this project - like you have to check whether or not you have internet connection before making any FBSession requests. Look up Reachability Sample Code over at Apple website for that.

Ok Good luck and have fun!