Include Facebook “Post to your wall” in your App

I’m somhow a newbee in developing iPhone-Apps. This means a lot of searching in iNet, try-and-error and “throwning the mac out of the window”…

My task was to implement social networks in my app.
Twitter: 15 minutes
Mail: 2 hours
Facebook: 8 days (!!)

The examples from facebook, which are delivered with their framework, are somehow really good, but they do to much things for me. I only wanted to post to the users wall, nothing more. So I took the code from their samples, include them in my app -> DAMN, nothing works anymore.Then I searched a lot for other examples, and they all are good, if you want to see friend lists, post fotos or other things to the wall and the newsfeed, but a simple Wall-Post was not included their.

Other frameworks like ShareKit are also doing to much for me, so I started to try it on my own. I took the “Hackbook”-Example from facebook and reduces the functionality of it to what I only want to have: login, authorizise the app at your facebook account and post to the wall.

Afterwards I took this rest of code and implement it in my own app. But it was really a tie: The reduced example project worked exactly like I want but the same code in my app had two big problems:

  1. The app did not store the successfully authorization. After I stop the app and call it again, the authorization screen was shown again.
  2. After the authorization process the “Post to Wall” Popup was not opening. I had to call it manually again.

The problem was, that the Facebook example uses only one UIViewController as RootViewController in the AppDelegate. In my own App I am using a UITabBarController, which means, all Events must pass the TabBarController over the including NavigationController to to ViewController and somewhere there are “lost in space”.

Then I took a look to the Source Code from other Frameworks how they have done it there and afterward I married the results with the reduced Hackbook example and now it works fine.

If anybody else has a problem like I had, I will post my results here to help you maybe.

Download the FacebookSDK.framework from Facebook: http://developers.facebook.com/ios/downloads/. I’m using the version 3.0.8

Include the facbookSDK.framework in your “Link Binary With Libaries” part.

Drag the FacebookSDK.framework/Versions/A/DeprecatedHeaders folder to the Frameworks section of your app. Important: Choose ‘Create groups for any added folders’ and deselect ‘Copy items into destination group’s folder (if needed)’. This adds the headers as a reference.

Import FBConnect.h to your AppDelegate.h and all ViewControllers (.h and .m), where you want to implement the share button.

#import "FBConnect.h"

create an configuration.h file, which you can include in all .m files where you want to implement the share button. Here define the enum for the apiCall events:

 

typedef enum apiCall {
 
    kAPILogout,
 
    kAPIGraphUserPermissionsDelete,
 
    kDialogPermissionsExtended,
 
    kDialogRequestsSendToMany,
 
    kAPIGetAppUsersFriendsNotUsing,
 
    kAPIGetAppUsersFriendsUsing,
 
    kAPIFriendsForDialogRequests,
 
    kDialogRequestsSendToSelect,
 
    kAPIFriendsForTargetDialogRequests,
 
    kDialogRequestsSendToTarget,
 
    kDialogFeedUser,
 
    kAPIFriendsForDialogFeed,
 
    kDialogFeedFriend,
 
    kAPIGraphUserPermissions,
 
    kAPIGraphMe,
 
    kAPIGraphUserFriends,
 
    kDialogPermissionsCheckin,
 
    kDialogPermissionsCheckinForRecent,
 
    kDialogPermissionsCheckinForPlaces,
 
    kAPIGraphSearchPlace,
 
    kAPIGraphUserCheckins,
 
    kAPIGraphUserPhotosPost,
 
    kAPIGraphUserVideosPost,
 
} apiCall;

In your AppDelegate.h, define local and public variables for Facebook and userPermissions.

 

@interface AppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate, UINavigationControllerDelegate> {
 
    UIWindow *window; // The main Window of the application
 
    UITabBarController *myTabBarController; // the tab bar controller is the root view controller of the app
 
    Facebook *facebook;
 
    NSMutableDictionary *userPermissions;
 
}
 
 @property (nonatomic, retain) IBOutlet UIWindow *window;
 
@property (nonatomic, retain) IBOutlet UITabBarController *myTabBarController;
 
@property (nonatomic, retain) Facebook *facebook;
 
@property (nonatomic, retain) NSMutableDictionary *userPermissions;
 
 + (Facebook *)sharedInstance;
 
 @end

Also define the class method sharedInstance for creating a Singleton of Facebook.

In your AppDelegate.m, define the Facebook AppID and initialize the facebook instance with the singleton.

 

static NSString* kAppId = @"236618989793547";
 
@implementation AppDelegate
 
 @synthesize window, myTabBarController, facebook, userPermissions;
 
 + (Facebook *)sharedInstance {
 
    static Facebook *gFacebook = NULL;
 
    @synchronized(self) {
 
        if (gFacebook == NULL)
 
            gFacebook = [[Facebook alloc] initWithAppId:kAppId andDelegate:[[ViewController alloc] initWithIndex:2]];
 
    }
 
    return(gFacebook);
 
}
 
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
 
    facebook = (Facebook *) [AppDelegate sharedInstance];
 
    // Check and retrieve authorization information
 
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
 
    if ([defaults objectForKey:@"FBAccessTokenKey"] && [defaults objectForKey:@"FBExpirationDateKey"]) {
 
        facebook.accessToken = [defaults objectForKey:@"FBAccessTokenKey"];
 
        facebook.expirationDate = [defaults objectForKey:@"FBExpirationDateKey"];
 
    }
 
    userPermissions = [[NSMutableDictionary alloc] initWithCapacity:1];
 
    // Check App ID:
 
    // This is really a warning for the developer, this should not happen in a completed app
 
    if (!kAppId) {
 
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Setup Error" message:@"Missing app ID. You cannot run the app until you provide this in the code." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
 
        [alertView show];
 
        [alertView release];
 
    } else {
 
        // Now check that the URL scheme fb[app_id]://authorize is in the .plist and can
 
        // be opened, doing a simple check without local app id factored in here
 
        NSString *url = [NSString stringWithFormat:@"fb%@://authorize",kAppId];
 
        BOOL bSchemeInPlist = NO; // find out if the sceme is in the plist file.
 
        NSArray* aBundleURLTypes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"];
 
        if ([aBundleURLTypes isKindOfClass:[NSArray class]] &&
 
            ([aBundleURLTypes count] > 0)) {
 
            NSDictionary* aBundleURLTypes0 = [aBundleURLTypes objectAtIndex:0];
 
            if ([aBundleURLTypes0 isKindOfClass:[NSDictionary class]]) {
 
                NSArray* aBundleURLSchemes = [aBundleURLTypes0 objectForKey:@"CFBundleURLSchemes"];
 
                if ([aBundleURLSchemes isKindOfClass:[NSArray class]] &&
 
                    ([aBundleURLSchemes count] > 0)) {
 
                    NSString *scheme = [aBundleURLSchemes objectAtIndex:0];
 
                    if ([scheme isKindOfClass:[NSString class]] &&
 
                        [url hasPrefix:scheme]) {
 
                        bSchemeInPlist = YES;
 
                    }
 
                }
 
            }
 
        }
 
        // Check if the authorization callback will work
 
        BOOL bCanOpenUrl = [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString: url]];
 
        if (!bSchemeInPlist || !bCanOpenUrl) {
 
            UIAlertView *alertView = [[UIAlertView alloc]  initWithTitle:@"Setup Error" message:@"Invalid or missing URL scheme. You cannot run the app until you set up a valid URL scheme in your .plist." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
 
            [alertView show];
 
            [alertView release];
 
        }
 
    }
 
    // .... Do whatever your app should do
 
}
 
- (void)applicationDidBecomeActive:(UIApplication *)application {
 
    // Although the SDK attempts to refresh its access tokens when it makes API calls,
 
    // it's a good practice to refresh the access token also when the app becomes active.
 
    // This gives apps that seldom make api calls a higher chance of having a non expired
 
    // access token.
 
    [[self facebook] extendAccessTokenIfNeeded];
 
}
 
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
 
    return [self.facebook handleOpenURL:url];
 
}
 
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
 
    return [self.facebook handleOpenURL:url];
 
}
 
- (void)dealloc {
 
[window release];
 
    [facebook release];
 
    [userPermissions release];
 
    [super dealloc];
 
}

The ViewControllers .h files needs to implement now the Facebook Delegation methods and a lot of needed variables, which I copied from the Hackbook example:

#import "FBConnect.h"
 
@interface ViewController : UITableViewController <FBDialogDelegate, FBSessionDelegate, FBRequestDelegate> {
    int currentAPICall;
 
    NSUInteger childIndex;
 
    NSMutableArray *apiMenuItems;
 
    NSString *apiHeader;
 
    NSMutableArray *savedAPIResult;
 
    UIActivityIndicatorView *activityIndicator;
 
    UILabel *messageLabel;
 
    UIView *messageView;
 
    NSArray *permissions;
 
}
 
@property (nonatomic, retain) AppDelegate *appDelegate; // include appDelegate to read global variables
 
@property (nonatomic, retain) NSMutableArray *apiMenuItems;
 
@property (nonatomic, retain) NSString *apiHeader;
 
@property (nonatomic, retain) NSMutableArray *savedAPIResult;
 
@property (nonatomic, retain) UIActivityIndicatorView *activityIndicator;
 
@property (nonatomic, retain) UILabel *messageLabel;
 
@property (nonatomic, retain) UIView *messageView;
 
@property (nonatomic, retain) NSArray *permissions;
 
- (id)initWithIndex:(NSUInteger)index;
 
 @end

 

At last TODO, the .m files of the viewControllers must code the needed Methods, to call the facebook SDK correctly.

 

#import "FBConnect.h"
 
@implementation ViewController
 
@synthesize appDelegate, apiHeader, apiMenuItems, savedAPIResult, activityIndicator, messageView, messageLabel, permissions;
 
- (id)initWithIndex:(NSUInteger)index {
 
    self = [super init];
 
    if (self) {
 
        childIndex = index;
 
        savedAPIResult = [[NSMutableArray alloc] initWithCapacity:1];
 
    }
 
    return self;
 
}
 
- (void)viewWillDisappear:(BOOL)animated {
 
    [super viewWillDisappear:animated];
 
    // Hide the activitiy indicator
 
    [self hideActivityIndicator];
 
    // Hide the message.
 
    [self hideMessage];
 
}
 
- (void)dealloc {
 
    [permissions release];
 
    [apiMenuItems release];
 
    [apiHeader release];
 
    [savedAPIResult release];
 
    [activityIndicator release];
 
    [messageLabel release];
 
    [messageView release];
 
[super dealloc];
 
}
 
- (void)viewWillAppear:(BOOL)animated {
 
[super viewWillAppear:animated];
 
    // Activity Indicator
 
    int xPosition = (self.view.bounds.size.width / 2.0) - 15.0;
 
    int yPosition = (self.view.bounds.size.height / 2.0) - 15.0;
 
    activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(xPosition, yPosition, 30, 30)];
 
    activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
 
    [self.view addSubview:activityIndicator];
 
    // Message Label for showing confirmation and status messages
 
    CGFloat yLabelViewOffset = self.view.bounds.size.height-self.navigationController.navigationBar.frame.size.height-30;
 
    messageView = [[UIView alloc] initWithFrame:CGRectMake(0, yLabelViewOffset, self.view.bounds.size.width, 30)];
 
    messageView.backgroundColor = [UIColor lightGrayColor];
 
    UIView *messageInsetView = [[UIView alloc] initWithFrame:CGRectMake(1, 1, self.view.bounds.size.width-1, 28)];
 
    messageInsetView.backgroundColor = [UIColor colorWithRed:255.0/255.0 green:248.0/255.0 blue:228.0/255.0 alpha:1];
 
    messageLabel = [[UILabel alloc]
 
                    initWithFrame:CGRectMake(4, 1, self.view.bounds.size.width-10, 26)];
 
    messageLabel.text = @"";
 
    messageLabel.font = [UIFont fontWithName:@"Helvetica" size:14.0];
 
    messageLabel.backgroundColor = [UIColor colorWithRed:255.0/255.0 green:248.0/255.0 blue:228.0/255.0 alpha:0.6];
 
    [messageInsetView addSubview:messageLabel];
 
    [messageView addSubview:messageInsetView];
 
    [messageInsetView release];
 
    messageView.hidden = YES;
 
    [self.view addSubview:messageView];
 
    // .... DO NEEDED STUFF FOR THE VIEW
 
}
 
- (void)facebookButtonClicked {
 
    if (![(Facebook *) [AppDelegate sharedInstance]  isSessionValid]) {
 
        permissions = [[NSArray alloc] initWithObjects:@"publish_stream",@"offline_access", nil];
 
        [(Facebook *) [AppDelegate sharedInstance] authorize:permissions];
 
        // News Feed, NEEDED PART OF THE DataSet.m FILE
 
        NSDictionary *newsMenu1 = [[NSDictionary alloc] initWithObjectsAndKeys: @"Publish to the user's wall", @"title", @"This allows a user to post something to their own Wall, which means it will also appear in all of their friends' News Feeds on Facebook.", @"description", @"Publish to your wall", @"button", @"apiDialogFeedUser", @"method", nil];
 
        NSArray *newsMenuItems = [[NSArray alloc] initWithObjects: newsMenu1, nil];
 
        NSDictionary *newsConfigData = [[NSDictionary alloc] initWithObjectsAndKeys: @"News Feed", @"title", @"Your app can prompt users to share on their own wall or their friend's wall.", @"description", @"http://developers.facebook.com/docs/channels/", @"link", newsMenuItems, @"menu", nil];
 
        apiMenuItems = [[NSArray arrayWithArray:[newsConfigData objectForKey:@"menu"]] retain];
 
        apiHeader = [[newsConfigData objectForKey:@"description"] retain];
 
        [(Facebook *) [AppDelegate sharedInstance]  authorize:permissions];
 
    } else {
 
        [self apiButtonClicked];  
 
    }
 
}
 
#pragma mark
 
#pragma Facebook functionality
 
- (void)didReceiveMemoryWarning {
 
    // Releases the view if it doesn't have a superview.
 
    [super didReceiveMemoryWarning];
 
    // Release any cached data, images, etc that aren't in use.
 
}
 
#pragma mark - Private Helper Methods
 
/*
 
 * This method shows the activity indicator and
 
 * deactivates the table to avoid user input.
 
 */
 
- (void)showActivityIndicator {
 
    if (![activityIndicator isAnimating]) {
 
        [activityIndicator startAnimating];
 
    }
 
}
 
/*
 
 * This method hides the activity indicator
 
 * and enables user interaction once more.
 
 */
 
- (void)hideActivityIndicator {
 
    if ([activityIndicator isAnimating]) {
 
        [activityIndicator stopAnimating];
 
    }
 
}
 
/*
 
 * This method is used to display API confirmation and
 
 * error messages to the user.
 
 */
 
- (void)showMessage:(NSString *)message {
 
    CGRect labelFrame = messageView.frame;
 
    labelFrame.origin.y = [UIScreen mainScreen].bounds.size.height - self.navigationController.navigationBar.frame.size.height - 20;
 
    messageView.frame = labelFrame;
 
    messageLabel.text = message;
 
    messageView.hidden = NO;
 
    // Use animation to show the message from the bottom then
 
    // hide it.
 
    [UIView animateWithDuration:0.5 delay:1.0 options: UIViewAnimationCurveEaseOut animations:^{
 
        CGRect labelFrame = messageView.frame;
 
        labelFrame.origin.y -= labelFrame.size.height;
 
        messageView.frame = labelFrame;
 
    }
 
            completion:^(BOOL finished){
 
                if (finished) {
 
                    [UIView animateWithDuration:0.5 delay:3.0 options: UIViewAnimationCurveEaseOut animations:^{
 
                        CGRect labelFrame = messageView.frame;
 
                        labelFrame.origin.y += messageView.frame.size.height;
 
                        messageView.frame = labelFrame;
 
                    }
 
            completion:^(BOOL finished){
 
                if (finished) {
 
                    messageView.hidden = YES;
 
                    messageLabel.text = @"";
 
                }
 
            }];
 
        }
 
    }];
 
}
 
/*
 
 * This method hides the message, only needed if view closed
 
 * and animation still going on.
 
 */
 
- (void)hideMessage {
 
    messageView.hidden = YES;
 
    messageLabel.text = @"";
 
}
 
/**
 
 * Helper method called when a button is clicked
 
 */
 
- (void)apiButtonClicked {
 
    /*SEL selector = NSSelectorFromString([[apiMenuItems objectAtIndex:0] objectForKey:@"method"]);
 
    if ([self respondsToSelector:selector]) {
 
        [self performSelector:selector];
 
    }*/
 
    [self apiDialogFeedUser];
 
}
 
/**
 
 * Helper method to parse URL query parameters
 
 */
 
- (NSDictionary *)parseURLParams:(NSString *)query {
 
NSArray *pairs = [query componentsSeparatedByString:@"&"];
 
NSMutableDictionary *params = [[[NSMutableDictionary alloc] init] autorelease];
 
for (NSString *pair in pairs) {
 
NSArray *kv = [pair componentsSeparatedByString:@"="];
 
NSString *val =  [[kv objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
 
[params setObject:val forKey:[kv objectAtIndex:0]];
 
}
 
    return params;
 
}
 
/**
 
 * --------------------------------------------------------------------------
 
 * News Feed
 
 * --------------------------------------------------------------------------
 
 */
 
/*
 
 * Dialog: Feed for the user
 
 */
 
- (void)apiDialogFeedUser {
 
    currentAPICall = kDialogFeedUser;
 
    SBJSON *jsonWriter = [[SBJSON new] autorelease];
 
    // The action links to be shown with the post in the feed
 
    NSArray* actionLinks = [NSArray arrayWithObjects:[NSDictionary dictionaryWithObjectsAndKeys: @"Get Started",@"name",@"http://Link to the App",@"link", nil], nil];
 
    NSString *actionLinksStr = [jsonWriter stringWithObject:actionLinks];
 
    // Dialog parameters
 
    NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
 
                                   @"Name of the Page", @"name",
 
                                   @"Subtitle of the page", @"caption",
 
                                   @"Description of the Page", @"description",
 
                                   @"Link of the page", @"link",
 
                                   @"Link to Image", @"picture",
 
                                   actionLinksStr, @"actions",
 
                                   nil];
 
    [(Facebook *) [AppDelegate sharedInstance] dialog:@"feed" andParams:params andDelegate:self];
 
}
 
#pragma mark - FBSessionDelegate Methods
 
- (void)fbDidLogin {
 
    [self storeAuthData:[(Facebook *) [AppDelegate sharedInstance]  accessToken] expiresAt:[(Facebook *) [AppDelegate sharedInstance]  expirationDate]];
 
    [self userDidGrantPermission];
 
    [self apiButtonClicked];
 
}
 
-(void)fbDidExtendToken:(NSString *)accessToken expiresAt:(NSDate *)expiresAt {
 
    [self storeAuthData:accessToken expiresAt:expiresAt];
 
}
 
-(void)fbDidNotLogin:(BOOL)cancelled {
 
}
 
- (void)fbDidLogout {
 
}
 
- (void)fbSessionInvalidated {
 
}
 
- (void)storeAuthData:(NSString *)accessToken expiresAt:(NSDate *)expiresAt {
 
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
 
    [defaults setObject:accessToken forKey:@"FBAccessTokenKey"];
 
    [defaults setObject:expiresAt forKey:@"FBExpirationDateKey"];
 
    [defaults synchronize];
 
}
 
/**
 
 * Called when the user granted additional permissions.
 
 */
 
- (void)userDidGrantPermission {
 
    [self updateCheckinPermissions];
 
    [self apiGraphUserCheckins];
 
}
 
- (void)updateCheckinPermissions {
 
    [[appDelegate userPermissions] setObject:@"1" forKey:@"user_checkins"];
 
    [[appDelegate userPermissions] setObject:@"1" forKey:@"publish_checkins"];
 
}
/*
 
 * Graph API: Get the user's check-ins
 
 */
 
- (void)apiGraphUserCheckins {
 
    [self showActivityIndicator];
 
    currentAPICall = kAPIGraphUserCheckins;
 
    [(Facebook *) [AppDelegate sharedInstance]  requestWithGraphPath:@"me/checkins" andDelegate:self];
 
}
 
#pragma mark - FBDialogDelegate Methods
 
/**
 
 * Called when a UIServer Dialog successfully return. Using this callback
 
 * instead of dialogDidComplete: to properly handle successful shares/sends
 
 * that return ID data back.
 
 */
 
- (void)dialogCompleteWithUrl:(NSURL *)url {
 
    if (![url query]) {
 
        NSLog(@"User canceled dialog or there was an error");
 
        return;
 
    }
 
    NSDictionary *params = [self parseURLParams:[url query]];
 
    switch (currentAPICall) {
 
        case kDialogFeedUser:
 
        case kDialogFeedFriend:
 
        {
 
            // Successful posts return a post_id
 
            if ([params valueForKey:@"post_id"]) {
 
                [self showMessage:@"Published feed successfully."];
 
                NSLog(@"Feed post ID: %@", [params valueForKey:@"post_id"]);
 
            }
 
            break;
 
        }
 
        case kDialogRequestsSendToMany:
 
        case kDialogRequestsSendToSelect:
 
        case kDialogRequestsSendToTarget:
 
        {
 
            // Successful requests return one or more request_ids.
 
            // Get any request IDs, will be in the URL in the form
 
            // request_ids[0]=1001316103543&request_ids[1]=10100303657380180
 
            NSMutableArray *requestIDs = [[[NSMutableArray alloc] init] autorelease];
 
            for (NSString *paramKey in params) {
 
                if ([paramKey hasPrefix:@"request_ids"]) {
 
                    [requestIDs addObject:[params objectForKey:paramKey]];
 
                }
 
            }
 
            if ([requestIDs count] > 0) {
 
                [self showMessage:@"Sent request successfully."];
 
                NSLog(@"Request ID(s): %@", requestIDs);
 
            }
 
            break;
 
        }
 
        default:
 
            break;
 
    }
 
}
 
- (void)dialogDidNotComplete:(FBDialog *)dialog {
 
    NSLog(@"Dialog dismissed.");
 
}
 
- (void)dialog:(FBDialog*)dialog didFailWithError:(NSError *)error {
 
    NSLog(@"Error message: %@", [[error userInfo] objectForKey:@"error_msg"]);
 
    [self showMessage:@"Oops, something went haywire."];
 
}

Now everything works like it should.

 

 

 

 

 

Print Friendly

One thought on “Include Facebook “Post to your wall” in your App

Leave a Reply

Your email address will not be published. Required fields are marked *