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:
- The app did not store the successfully authorization. After I stop the app and call it again, the authorization screen was shown again.
- 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.
One thought on “Include Facebook „Post to your wall“ in your App”
My usual wavy parallel lines might be a little too boring with so much open space.