In my previous tutorial, I presented an easy and fast way to implement the login with Facebook feature. Based on the FBLoginView class, I demonstrated how logging in and out from Facebook can be done in really a few minutes with the assistance of a predefined login button. In this tutorial we’ll do the exact same thing, but this time we won’t use any automatic solution. Instead, we will implement every part of the login process manually and we’ll see in some detail level how everything works when connecting to Facebook. That’s a useful knowledge to have, as there are many cases that the default login view we saw in the last tutorial isn’t handful at all.
Before we start programming, it is necessary to present some basic terms, so you’ve got an idea about the whole process. The first important thing you should know, is that after a successful login with Facebook the app receives an access token. This is a unique key, that allows to perform requests and exchange data with Facebook in a secure fashion, without having to authorize the app all the time. One could say that the whole login hassle happens just to get that access token. Once received, and depending on the permissions your app asks for, you can freely get and send data.
The key class of the Facebook SDK that handles everything that has to do with the access token, is the FBSession class. This one, manages all the various session states that a connection can have, and is the one that determines the logged in status. It would be useful to take a quick look at the state lifecycle, as that would help to understand what we’ll do next. Let’s see some details:
- When an app is launched, the initial session state, meaning the initial state value set to the FBSession class by default, is the FBSessionStateCreated. The class searches for an existing, stored access token, and if founds one, it goes to the FBSessionStateCreatedTokenLoaded, and then to the FBSessionStateOpen, where the session is open and data exchange can take place with no problem.
- When no access token exists and a login process is on the way, then from the initial FBSessionStateCreated state, when the login UI is appeared the state changes to FBSessionStateCreatedOpening. If all goes well on login/app authorization, then the state gets the FBSessionStateOpen value. However, if during the credentials entry or on app authorization the user taps on the Cancel button and stops the login process, then the state gets the FBSessionStateClosedLoginFailed value.
- There are cases where the initial permissions asked during login are not enough, so additional permissions are needed to be granted. If that successfully takes place, then the state changes its value from FBSessionStateOpen to FBSessionStateOpenTokenExtended. Note that no extra permissions can be asked if the current session state doesn’t have the FBSessionStateOpen value.
- Finally, when logging out, then from any of the two open states, the FBSessionStateOpen and the FBSessionStateOpenTokenExtended, the session ends up with the FBSessionStateClosed state value.
In the above description, I mentioned a couple of times the term “permissions”, so now let me make a comment on them. Permissions actually determine what assets, features or resources an app can have access to, and they are separated in two categories: The read and publish permissions. To the read category belong all those that describe access to user info, while to the second one belong all those that allow an app to publish on behalf of the user. If no permissions are requested for a specific resource, then accessing it is impossible. A login process is initiated using read permissions, and always the public user info, or in other words the “public_profile” permission, is granted by default. When users login with Facebook through an app may become hesitant when they’re asked to grant permissions, so make sure to ask only for those permissions that are necessary for the app. We’ll talk about permission more later in the tutorial as well.
Finally, a word about the Graph API, as this is a term you’ll meet in the sections that follow. The Graph API, is actually the Facebook API for communicating and exchanging data. Is HTTP-based, and it’s used for all the tasks you want to do with Facebook. Regarding the Facebook SDK for iOS, there is a wide number of methods that work with it. In this tutorial we’ll use just a couple of them. If you are curious though and you’d like to see more about it, then feel free to read more on Facebook documentation. Of course, if you’re about to develop an app that works with Facebook and does more than logging users in, you definitely need to read some stuff.
All the discussion made so far, will help you understand all that coming next. If you need more theory or to get fundamental knowledge, then there’s no better option than visiting the source itself, meaning the Facebook documentation. For now, we’ve said enough already, so let’s begin doing some work.
App Overview
The next figure illustrates the final outcome of our implementation in this tutorial:
The main goal is to manage to login with Facebook, and then logout, using a custom button. Further than doing that, we’ll also get some piece of the user’s public info, the e-mail address and the profile picture after a successful login.
The UI will contain a few controls: Some labels for showing the user’s info and the current connection status, an image view for displaying the profile picture, an activity indicator that will animate when opening a session, and of course, the custom login/logout button. We’ll see more details about all these subviews when we’ll setup the interface.
Lastly, there’s a dedicated section in this tutorial almost for every part of the sample app that’s about to be implemented. Even though we’ll pay some attention to create the app, we’ll mostly focus on the login implementation, and all the related stuff.
Note that if you download the sample project, make sure to correctly enter your app’s FacebookAppID, FacebookDisplayName string values and the URL Types array. Also, you must necessarily add the Facebook SDK framework to the project. For more information about all that, see the Initial Steps section of the tutorial.
Having said all that, let’s get started.
Creating the App
As always, the first step we have to do is to create a new sample project. Therefore, launch Xcode and do so. In the initial guide that appears, select the Single View Application as the project template, in the Applications category, under the iOS section.
Click Next to proceed. In the second step of the guide, in the Product Name field set the ManualFBLogin as the project’s name. Also, make sure that in the Devices drop down menu, the iPhone option is selected.
Click Next, and in the third and last step of the guide select a directory to save the project and then click on the Create button.
Initial Steps
This part is very crucial, as it involves the necessary setup needed to be done in both Facebook developers website and Xcode project before writing even a single line of code. In the previous tutorial regarding the Facebook login, I presented in details all the actions you should take, so go in the Preparing the Environment section and follow everything described there step by step. Here’s a summary of what you should do:
- Create a new app record on the Facebook developers website.
- Fill in all the necessary fields, and correctly copy the app’s bundle ID.
- Add the Facebook SDK framework to the project.
- In the .plist file of the project add all the needed keys and the respective values properly.
When you are ready with all the above, you can return here and continue with this tutorial. Don’t skip this step.
Interface Setup
Our goal here is to configure the interface by adding all the necessary subviews to the View Controller scene, and by declaring and connecting all the respective IBOutlet properties. We will work in the Interface Builder, so click on the Main.storyboard file to let it appear.
The following screenshot demonstrates what the final outcome of our work here will be.
The subviews we must add to the interface are the next ones:
- An image view to display the user’s profile picture.
- A label to show the user’s full name (first and last name).
- A label to show the user’s e-mail address.
- A label centered in the view that will display the status.
- An activity indicator view that will animate when the app is opening an existing session.
- A button at the bottom of the view, that will be used to log in and out.
As you notice, the login button is fully customized and it has nothing to do with the login view, the pre-made login button of the Facebook SDK.
Let’s start adding all the subviews one by one and setting all the appropriate attributes of them. For each subview described below, find the appropriate object in the Object Library and drag and drop it in the default view.
UIImageView
- Frame: X=20, Y=60, Width=63, Height=63
UILabel
- Frame: X=91, Y=60, Width=209, Height=63
- Text: None or Fullname
- Text Color: White
- Font: System Bold, 15.0
- Text Alignment: Center
- Lines: 5
- Line Break: Word Wrap
UILabel
- Frame: X=20, Y=148, Width=280, Height=21
- Text: None or E-mail
- Text Color: White
- Font: System Bold, 15.0
- Text Alignment: Center
UILabel
- Frame: X=20, Y=273, Width=280, Height=21
- Text: You are not logged in.
- Text Color: White
- Font: System Bold, 17.0
- Text Alignment: Center
UIActivityIndicator
- Frame: X=150, Y=302, Width=20, Height=20
- Style: White
UIButton
- Frame: X=0, Y=508, Width=320, Height=60
- Title: Login
- Text Color: White
- Font: System Bold, 15.0
- Background Color: R=255, G=128, B=0
Finally, set the view’s background color to R=59, G=89, B=152.
Your scene should now look like the screenshot illustrated at the beginning of this part.
Now that all the necessary subviews have been added to the view, it’s time to declare some IBOutlet properties and then connect them to these subviews. Open the ViewController.h file and add the next lines:
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIButton *btnToggleLoginState;
@property (weak, nonatomic) IBOutlet UIImageView *imgProfilePicture;
@property (weak, nonatomic) IBOutlet UILabel *lblFullname;
@property (weak, nonatomic) IBOutlet UILabel *lblEmail;
@property (weak, nonatomic) IBOutlet UILabel *lblStatus;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
@end
The name of each property clearly declares its purpose. Further than the above, let’s also declare an IBAction method that we’ll use to login and logout. Add the next line right after the last property declaration:
- (IBAction)toggleLoginState:(id)sender;
Finally, head back to the Main.storyboard file, and connect the IBOutlet properties to the appropriate subviews. Here are the matches:
- The btnToggleLoginState property must be connected to the login button at the bottom.
- The imgProfilePicture property must be connected to the image view.
- The lblFullname property must be connected to the label next to the image view.
- The lblEmail property must be connected to the e-mail label, right under the image view.
- The lblStatus property must be connected to the status label to the center of the view.
- The activityIndicator property must be connected to the activity indicator view.
Here is a figure illustrating a property connection:
Additional In-Code Configuration
Having finished with the interface configuration, it’s time to move on to code. Before we dive into the heart of the topic though, we will perform some code-level setup that will determine the initial state of the app. Note that what we’ll do here is not required for the Facebook SDK to work. However it will help you make your project similar to the sample in case you develop following this tutorial step by step. So, here’s in short the most important tasks we’ll do:
- We will create a private method to set the hidden state of the subviews.
- We will add the necessary code needed to make the image view rounded.
- We will change the status bar style, making it have a light color.
Let’s begin with the easy one. Open the ViewController.m file, and in the viewDidLoad method add the next line:
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
This will turn the dark status bar into a light one.
Next let’s make the image view rounded, with a white border around it. In order to do that, we must access the CALayer layer property of it, and set three specific properties of the layer object. However the CALayer class is not part of the UIKit framework, therefore we must import the QuartzCore framework. In the top of the file add the next line:
#import
The following four lines make the image view rounded, with a white border and 1px thick. Make sure to add them in the viewDidLoad
method.
self.imgProfilePicture.layer.masksToBounds = YES;
self.imgProfilePicture.layer.cornerRadius = 30.0;
self.imgProfilePicture.layer.borderColor = [UIColor whiteColor].CGColor;
self.imgProfilePicture.layer.borderWidth = 1.0;
Now, let’s create a private method that will enable us to hide and show all the user info related subviews, depending on whether the user is logged in or not. At first, we must declare it in the private class section:
@interface ViewController ()
-(void)hideUserInfo:(BOOL)shouldHide;
@end
The shouldHide parameter flag will specify if the hidden property of the following subviews will be YES or NO. The method definition:
-(void)hideUserInfo:(BOOL)shouldHide{
self.imgProfilePicture.hidden = shouldHide;
self.lblFullname.hidden = shouldHide;
self.lblTotalFriends.hidden = shouldHide;
}
Initially, we want all of the above subviews to be hidden. Later, when the connected state will be determined using the Facebook SDK, they’ll become visible if needed. So, let’s go to the viewDidLoad
method and make a call to this method:
[self hideUserInfo:YES];
Besides than making those subviews hidden, it’s necessary to hide the activity indicator as well. We wouldn’t like the spinner appear with no reason. So, right below the above method call, add the next line:
self.activityIndicator.hidden = YES;
Note that the activity indicator will be handled separately from the rest of the subviews. The same applies to the status label.
Finally, let’s declare and instantiate an AppDelegate
property, which we are going to use later during the implementation. Firstly, import the AppDelegate.h
header file:
#import "AppDelegate.h"
Next, go to the private class section and add a property declaration as follows:
@property (nonatomic, strong) AppDelegate *appDelegate;
Lastly, in the viewDidLoad
method, instantiate it:
self.appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
Now we are ready to focus on the login implementation.
Logging In
The Facebook SDK contains a great number of libraries and classes. Among them, there is one class that is responsible for handling sessions, therefore its job is to handle the login process as well. That class is named FBSession
, and it’s the one we need in order to achieve our goal.
The FBSession
class implements the next method:
openActiveSessionWithReadPermissions:allowLoginUI:completionHandler:
With this one the login process is started. Let’s take a quick look at its parameters:
- openActiveSessionWithReadPermissions: The read permissions that are required by the app are provided here. For your knowledge, it’s not necessary to request for all permissions at once, as the more they are, the less possible is for the user to authorize and use the app. Additional permissions can be asked during the app execution. There are publish permissions too that can be requested along the way, but we don’t care about any of them right now. In this example, we will ask for the public_profile and the email permissions. You can find a full reference about permissions here.
- allowLoginUI: This parameter accepts a YES or NO value. If YES, then it shows either the Facebook app (if installed) or Safari to enter credentials if needed, and then to authorize the app.
- completionHandler: The completion handler block is called when the login process has finished. It returns three parameters: A session (FBSession) object, the session state value, and an error object in case it occurs any. We’ll talk more about the session and its various states in the next section of the tutorial.
Let’s start working now, and we’ll discuss every detail along the way. The “entry” point that we’ll begin from, is the toggleLoginState: IBAction method we had previously declared. Using this method, we’ll initiate both the login and the logout operations. The first thing we have to consider, is to check whether there is an open session or not. In case there isn’t any, then we’ll use the method described above to login. However, prior to implementation, make sure to import the Facebook framework at the top of the ViewController.m file:
#import
Then, let’s see the initial implementation of the IBAction method:
- (IBAction)toggleLoginState:(id)sender {
if ([FBSession activeSession].state != FBSessionStateOpen &&
[FBSession activeSession].state != FBSessionStateOpenTokenExtended) {
}
else{
}
}
A couple of observations here:
- The current session, also named active session, is accessed just like this: [FBSession activeSession].
- Using the state property of the active session, we determine if there is an open session or not. The two open session states are shown above.
Now, instead of directly calling the openActiveSessionWithReadPermissions:allowLoginUI:completionHandler: method of the FBSession class, we’ll make a small detour. We’ll implement a public method in the AppDelegate class, and in there we’ll make the call. That might seem strange to you at the moment, but I have two specific reasons to do that:
- Later on, we’ll have to use the openActiveSessionWithReadPermissions:allowLoginUI:completionHandler: method again to open a stored session, and it wouldn’t be a good idea to write the same code twice.
- It is a more general solution that can be used in larger projects.
Having said that, open the AppDelegate.h file, and firstly import the Facebook framework:
#import
Then add the next method declaration:
@interface AppDelegate : UIResponder
@property (strong, nonatomic) UIWindow *window;
-(void)openActiveSessionWithPermissions:(NSArray *)permissions allowLoginUI:(BOOL)allowLoginUI;
@end
The parameters we’ll provide to the openActiveSessionWithPermissions:allowLoginUI: custom method will be passed to the openActiveSessionWithReadPermissions:allowLoginUI:completionHandler: of the FBSession class. What follows next is the method’s definition, but what are we going to do there?
Well, the answer lies to the completion handler of the openActiveSessionWithReadPermissions:allowLoginUI:completionHandler: method, where we must handle the various session states. In our case, after the user has logged in, we must update the UI and show the user’s info. The same should happen later on, when we’ll open an existing session on a subsequent app launch. However, we can’t access the ViewController‘s interface through the AppDelegate class directly. The simplest solution would be to inform the ViewController when the login/session opening process is over, and perform in that class all the session handling. A simple way to do that is using a notification. Indeed, we’ll post a notification when the completion handler will be called, and along with it we’ll post the completion handler’s results (session, session state, error).
So, speaking with code this time, go to the AppDelegate.m file to define the public method. Here it is:
-(void)openActiveSessionWithPermissions:(NSArray *)permissions allowLoginUI:(BOOL)allowLoginUI{
[FBSession openActiveSessionWithReadPermissions:permissions
allowLoginUI:allowLoginUI
completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
// Create a NSDictionary object and set the parameter values.
NSDictionary *sessionStateInfo = [[NSDictionary alloc] initWithObjectsAndKeys:
session, @"session",
[NSNumber numberWithInteger:status], @"state",
error, @"error",
nil];
// Create a new notification, add the sessionStateInfo dictionary to it and post it.
[[NSNotificationCenter defaultCenter] postNotificationName:@"SessionStateChangeNotification"
object:nil
userInfo:sessionStateInfo];
}];
}
You see that we initialize a NSDictionary object and we set all the values returned by the completion handler to it. Then, a new NSNotification object is posted, using the custom name SessionStateChangeNotification and the dictionary created right before.
Now, back at the toggleLoginState: IBAction method in the ViewController.m file, let’s make a call to the above method:
- (IBAction)toggleLoginState:(id)sender {
if ([FBSession activeSession].state != FBSessionStateOpen &&
[FBSession activeSession].state != FBSessionStateOpenTokenExtended) {
[self.appDelegate openActiveSessionWithPermissions:@[@"public_profile", @"email"] allowLoginUI:YES];
}
else{
}
}
To recap, when the user taps on the Login custom button, the openActiveSessionWithPermissions:allowLoginUI: public method of the AppDelegate is first called, which invokes in turn the openActiveSessionWithReadPermissions:allowLoginUI:completionHandler: of the FBSession class. Once the whole login process is over, we inform the ViewController using a notification, where we’ll handle the various session states in a while.
Before we reach the end of this section is necessary to perform a couple more steps: The first is to implement an application delegate method, the next one:
-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
}
This one is called after the login credentials entry and app authorization have finished in Facebook app or Safari. The [FBAppCall handleOpenURL:url sourceApplication:sourceApplication]
manages the results of all the actions taken outside the app (successful login/authorization or cancelation), and properly directs the login flow back in our app again.
The second step is to take our measures in case the user leaves the app while the login dialog is visible either in Facebook app or in Safari. In such a case, it’s necessary to use the Facebook framework for doing some cleanup and removing any unfinished session processes. Thankfully this is easy and it takes only a single line. Go to the applicationDidBecomeActive:
of the application delegate, and add the next one:
- (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.
[FBAppCall handleDidBecomeActive];
}
The handleDidBecomeActive method of the FBAppCall class will check for an unfinished login process at the next app launch, and it will clear all the unnecessary “leftovers”, if any found.
Handling Session States
Posting the notification after a finished login process is an important step, but it consists only of the half work that must be done. The other half regards two things: To observe for the notification and to handle the session states.
Let’s begin by observing for the notification. Open the ViewController.m file, and head to the viewDidLoad method. In there, add the following code snippet:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleFBSessionStateChangeWithNotification:) name:@"SessionStateChangeNotification" object:nil];
With that statement, we force our class to observe for the notification specified by the given name, and when it arrives we will call the handleFBSessionStateChangeWithNotification: method. This is a private method that we are about to implement right now.
Initially, go to the private class section and declare it:
@interface ViewController ()
-(void)handleFBSessionStateChangeWithNotification:(NSNotification *)notification;
@end
In its definition we’ll handle the session states we are interested in. These are:
- FBSessionStateOpen: When a session is open.
- FBSessionStateClosed: When a session is closed.
- FBSessionStateClosedLoginFailed: When the user cancels the login /authorization process.
Actually, here is what we are going to do:
- From the notification parameter object, we’ll extract the dictionary.
- We’ll assign to local variables the session state value and the error object which we’ll take from the dictionary.
- We’ll show the appropriate status message, along with the activity indicator.
- If no error occurred, then if the session is open we’ll make a call to Facebook Graph API to get all the info we want. If the session is closed or failed, we’ll update the UI.
- If an error occurred, we’ll output the error description and perform any UI-related tasks.
The next code fragment contains all these:
-(void)handleFBSessionStateChangeWithNotification:(NSNotification *)notification{
// Get the session, state and error values from the notification's userInfo dictionary.
NSDictionary *userInfo = [notification userInfo];
FBSessionState sessionState = [[userInfo objectForKey:@"state"] integerValue];
NSError *error = [userInfo objectForKey:@"error"];
self.lblStatus.text = @"Logging you in..."
[self.activityIndicator startAnimating];
self.activityIndicator.hidden = NO;
// Handle the session state.
// Usually, the only interesting states are the opened session, the closed session and the failed login.
if (!error) {
// In case that there's not any error, then check if the session opened or closed.
if (sessionState == FBSessionStateOpen) {
// The session is open. Get the user information and update the UI.
[self.btnToggleLoginState setTitle:@"Logout" forState:UIControlStateNormal];
}
else if (sessionState == FBSessionStateClosed || sessionState == FBSessionStateClosedLoginFailed){
// A session was closed or the login was failed or canceled. Update the UI accordingly.
[self.btnToggleLoginState setTitle:@"Login" forState:UIControlStateNormal];
self.lblStatus.text = @"You are not logged in."
self.activityIndicator.hidden = YES;
}
}
else{
// In case an error has occured, then just log the error and update the UI accordingly.
NSLog(@"Error: %@", [error localizedDescription]);
[self hideUserInfo:YES];
[self.btnToggleLoginState setTitle:@"Login" forState:UIControlStateNormal];
}
}
If you look closely at the code, you’ll notice that we’ve handled everything we care about, but we didn’t get the user’s info yet. That’s something we’ll do right next, as I’d like us to focus our attention a bit more on that.
Getting User Info
Summarizing what we’ve done so far, I’d say that the most important steps we have managed to complete are the login process initiation and the session state handling. What we haven’t still done, is to get the user’s information after a successful login or after having loaded an existing, and display it on-screen.
Getting user’s data is easy. It takes only a call to the startWithGraphPath:parameters:HTTPMethod:completionHandler: method of the FBRequestConnection class. As you conclude from the method’s name, it makes a HTTP request to the appropriate API and sends all the provided parameters, which are given as a dictionary object. This method is a general one and can be used for several kinds of requests.
Back to our sample app now, let me specify the kind of info we’ll ask for the connected user. We’re going to get the first name, the last name, the e-mail address and the profile picture URL, which we’ll then use for showing the user’s image on the view. The dictionary object, and according to the Facebook Graph API, is expressed as follows:
@{@"fields": @"first_name, last_name, picture.type(normal), email"}
Notice that for the profile picture we use the picture.type(normal) value, asking from Facebook to return the normal size picture. There are four picture types that can be used:
- Small
- Normal
- Large
- Square
The completion handler of the startWithGraphPath:parameters:HTTPMethod:completionHandler: method contains three parameters:
- A FBRequestConnection object. We won’t need it here.
- An id object that contains the actual data. In our case, this is going to be a NSDictionary object.
- An error pointer.
If no error occurred during the request, then we’ll get the user data from the returned dictionary and we’ll assign it as a value to the appropriate subview. Also, we’ll get the picture from the returned URL, and after all UI controls have taken their values, we’ll just unhide them. In case of an error, we’ll only output its description.
In the next code fragment it’s given the handleFBSessionStateChangeWithNotification: method implementation once again. However, this time contains the call to the startWithGraphPath:parameters:HTTPMethod:completionHandler: method in the case of an open session.
-(void)handleFBSessionStateChangeWithNotification:(NSNotification *)notification{
// Get the session, state and error values from the notification's userInfo dictionary.
NSDictionary *userInfo = [notification userInfo];
FBSessionState sessionState = [[userInfo objectForKey:@"state"] integerValue];
NSError *error = [userInfo objectForKey:@"error"];
self.lblStatus.text = @"Logging you in..."
[self.activityIndicator startAnimating];
self.activityIndicator.hidden = NO;
// Handle the session state.
// Usually, the only interesting states are the opened session, the closed session and the failed login.
if (!error) {
// In case that there's not any error, then check if the session opened or closed.
if (sessionState == FBSessionStateOpen) {
// The session is open. Get the user information and update the UI.
[FBRequestConnection startWithGraphPath:@"me"
parameters:@{@"fields": @"first_name, last_name, picture.type(normal), email"}
HTTPMethod:@"GET"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
// Set the use full name.
self.lblFullname.text = [NSString stringWithFormat:@"%@ %@",
[result objectForKey:@"first_name"],
[result objectForKey:@"last_name"]
];
// Set the e-mail address.
self.lblEmail.text = [result objectForKey:@"email"];
// Get the user's profile picture.
NSURL *pictureURL = [NSURL URLWithString:[[[result objectForKey:@"picture"] objectForKey:@"data"] objectForKey:@"url"]];
self.imgProfilePicture.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:pictureURL]];
// Make the user info visible.
[self hideUserInfo:NO];
// Stop the activity indicator from animating and hide the status label.
self.lblStatus.hidden = YES;
[self.activityIndicator stopAnimating];
self.activityIndicator.hidden = YES;
}
else{
NSLog(@"%@", [error localizedDescription]);
}
}];
[self.btnToggleLoginState setTitle:@"Logout" forState:UIControlStateNormal];
}
else if (sessionState == FBSessionStateClosed || sessionState == FBSessionStateClosedLoginFailed){
// A session was closed or the login was failed. Update the UI accordingly.
[self.btnToggleLoginState setTitle:@"Login" forState:UIControlStateNormal];
self.lblStatus.text = @"You are not logged in."
self.activityIndicator.hidden = YES;
}
}
else{
// In case an error has occurred, then just log the error and update the UI accordingly.
NSLog(@"Error: %@", [error localizedDescription]);
[self hideUserInfo:YES];
[self.btnToggleLoginState setTitle:@"Login" forState:UIControlStateNormal];
}
}
If you add a NSLog command to the above completion handler and print the result object, then you’ll get something like the next one:
2014-05-25 11:39:14.573 ManualFBLogin[748:90b] {
email = "[email protected]"
"first_name" = Gabriel;
id = 1377148289239707;
"last_name" = Tester;
picture = {
data = {
"is_silhouette" = 0;
url = "https://fbcdn-profile-a.akamaihd.net/hprofile-ak-xpf1/t1.0-1/s100x100/10390960_1392345714386631_6289945774589507432_s.jpg"
};
};
}
As you see, the picture object is a dictionary which in turn contains the data dictionary, and that’s the one containing the picture data we want. Based on that, it’s easy to understand how the NSURL object in the above method is formed:
NSURL *pictureURL = [NSURL URLWithString:[[[result objectForKey:@"picture"] objectForKey:@"data"] objectForKey:@"url"]];
Before we consider our work in this section done, I must say that using the startWithGraphPath:parameters:HTTPMethod:completionHandler: method is not the only way to get the user’s public info. Actually, there’s a method dedicated to that, the startForMeWithCompletionHandler:, and it’s part of the FBRequestConnection class. It has a disadvantage though, as it doesn’t return the URL of the profile picture and that’s why we didn’t use it in the first place.
If you wish so, feel free to make a call to that method and print to the debugger the results it brings back. Make sure to add it in the open session case, right before or after the startWithGraphPath:parameters:HTTPMethod:completionHandler: call. Here’s how you should invoke it:
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
NSLog(@"%@", result);
}
}];
This will output to the debugger something similar to this:
{
email = "[email protected]"
"first_name" = Gabriel;
gender = male;
id = 1377148289239707;
"last_name" = Tester;
link = "https://www.facebook.com/app_scoped_user_id/1377148289239707/"
locale = "en_US"
name = "Gabriel Tester"
timezone = 3;
"updated_time" = "2014-05-21T18:49:16+0000"
verified = 0;
}
Using this method just like that provides a nice way to find out what the public info regarding the user is, and what the names of the relevant fields are.
Now it’s a good time to run the app for first time. Initially, tap on the login button to get connected:
Provide your credentials and authorize the app:
If everything runs normally, then you’ll see your info appearing in the app, once you get back to it.
Opening a Session
If you try to run the app twice, you’ll notice that even though you’ve logged in the first time, you seem to be logged out upon the second launch. Actually, you are logged in indeed, but for the time being the app doesn’t know that. Fixing that situation is easy, but before we do so, let’s see some more details.
Every time that you successfully login with Facebook, the Facebook API returns some info regarding the connection, and saves it persistently. The most important piece of information contained is the access token that defines the session state and allows you to make requests without additional authorization to be needed upon subsequent app launches. Along with that token, its expiration date and some other stuff are included as well. The truth is that you don’t need to be aware of all these, as the Facebook SDK handles it behind the scenes. However,it is always good to know where everything is stored, so you can manually check if the proper info has been returned when debugging.
All the session-related info is stored into a .plist file, which you can find in a path similar to this (make sure that you have the hidden files visible):
/Users/gabriel/Library/Application Support/iPhone Simulator/7.1/Applications/A62A3F77-6A8C-477D-96FE-C32FB23B166B/Library/Preferences
The file of our app is named com.Your_Company_Name.ManualFBLogin.plist, and if you open it you can see if the token info is really there. Note that you shouldn’t edit it, otherwise expect something bad to happen in the app.
Having said all the above, let’s focus on our app again. As I said, for the time being the app doesn’t know if you are logged in or not, therefore will present you the default, logged out state on a subsequent launch. The goal is to make it read the above file in order to determine the connected state and then open the existing session. This should be done when the app is launched, and a good place to do so would be the application:didFinishLaunchingWithOptions: method of the application delegate. However, keep in mind that when the session state changes, we want to update our UI, and the problem that arises here is that this method is called before the view controller is loaded. That means that even if we load and open the session, the view controller won’t be ready soon enough to observe for the notification that will be posted upon a session state change, so the UI won’t be refreshed.
The solution in our case to load and open the session in the applicationDidBecomeActive: method of the AppDelegate class. If you remember, in a previous section of this tutorial we had added the next line in that method:
[FBAppCall handleDidBecomeActive];
Now, we must modify once again that method. In the next code segment is presented the code that must be added in order to properly open a saved session:
- (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.
if ([FBSession activeSession].state == FBSessionStateCreatedTokenLoaded) {
[self openActiveSessionWithPermissions:nil allowLoginUI:NO];
}
[FBAppCall handleDidBecomeActive];
}
Initially, the Facebook SDK checks if there’s a stored access token. If so, then we call the openActiveSessionWithPermissions:allowLoginUI: public method we had created previously, providing no permissions and not allowing the login UI to appear. That way, the app loads from the .plist file the saved session, and then posts the notification. The notification is received by the ViewController class, and ultimately the session is properly handled. If a valid access token exists, and if the session normally opens, then our app requests the user’s info from Facebook every time the app launches.
If you add the above few lines and you run the app again, you’ll see that this time you won’t seem to be logged out.
Before closing, there are three things I’d like to mention:
- The option to open the session in the applicationDidBecomeActive: is not a rule. I just chose to do so in this project, so the session opening works flawlessly with the custom notification posting and the session state handling. In your projects you could either choose this way, or open the session anywhere that best serves you.
- This is a sample project, so every time the app launches we request the user’s info from Facebook. In a real-world app though, and depending on the security level of your app, you could store the user’s info locally, and make requests to update it only when that’s necessary.
- In the beginning of this section I showed you where the access token along with the rest of the session info is saved to an app. However, Facebook allows to use a custom strategy for storing the session and managing all the procedures that deal with it (such as opening or loading it). That’s something out of the scope of this tutorial, so I won’t get into any details at all. I believe that you should be aware of that, so if you’d like to learn more you just have to visit the official Facebook documentation.
Logging Out
Everything is ready, apart from one thing. We still haven’t implemented the logout functionality, and that’s necessary for having a complete login mechanism.
Logging out is literally a matter of a single line of code. If you recall, in the toggleLoginState: IBAction method we have two cases: The first one where no open session exists and the user should login, and the opposite case (the else case), where the user is already logged in and now should log out.
In the next code fragment you are given the whole IBAction method, including the logout functionality. As you’ll see, we also update the UI accordingly.
- (IBAction)toggleLoginState:(id)sender {
if ([FBSession activeSession].state != FBSessionStateOpen &&
[FBSession activeSession].state != FBSessionStateOpenTokenExtended) {
[self.appDelegate openActiveSessionWithPermissions:@[@"public_profile", @"email"] allowLoginUI:YES];
}
else{
// Close an existing session.
[[FBSession activeSession] closeAndClearTokenInformation];
// Update the UI.
[self hideUserInfo:YES];
self.lblStatus.hidden = NO;
self.lblStatus.text = @"You are not logged in."
}
}
Compile and Run the App
Reaching almost at the end of the tutorial, it’s hard to imagine that you haven’t still compiled and run the app. However, if you haven’t done so, then this is the right time to do it. Before you click on the Run button on Xcode, make sure that you have properly configured both Facebook account and Xcode, and you followed everything step by step described in the previous sections.
The next animated graphics illustrate the whole process of login/logout we implemented in this tutorial:
Summary
This tutorial, in combination with the previous one regarding the Facebook login view, gives you all the tools you need for logging in with Facebook. As you see, it’s not difficult to do so as long as you follow some specific rules. I should mention that no special reference to error handling was made. That was in purpose, as it would be out of my goals to discuss it here. There’s an extensive description about that topic in Facebook documentation, so I’d advise you to give it a reading. Lastly, as a final word I would suggest to always stay up-to-date regarding the changes in Facebook SDK, as there might be significant modifications from version to version. I hope you’ve been helped by this post, and as always, leave us your thoughts.
For your reference, you can download the complete Xcode project from here.