Facebook API - How to cancel Graph Request - ios

I occasionally need to cancel a FaceBook graph request, but there seems to be no cancel or similar method in their API to do so. At the moment, crashes sometimes occur as the delegate I assigned to the request has been deallocated. Is there any way to cancel a graph request once submitted please?

I'm assuming you're talking about the facebook-ios-sdk project, and the lack of a cancel method in Facebook.h. I noticed this as well, and eventually decided to add my own cancel method. Just to note, the delegate you assign to the request shouldn't ever be dealloc'd and then referenced, because the request retains the delegate. See this similar question. Now, if you find yourself really needing a cancel method for some other reason...
Adding a cancel method:
Facebook requests are made in an opaque manner. You never see them, and only hear about results via the Facebook class. Under the hood, the Facebook class makes Graph API requests with the (not for public use) FBRequest class. This class is is basically a fancy NSURLConnection delegate. So to cancel the request, the member NSURLConnection just has to be told to cancel. Adding this method to FBRequest:
// Add to FBRequest.h
- (void)cancel;
And...
// Add to FBRequest.m
- (void)cancel {
[_connection cancel];
[_connection release], _connection = nil;
}
Now, to expose an interface in the Facebook class to make use of the new method...
// Add to Facebook.h
- (void)cancelPendingRequest;
And...
// Add to Facebook.m
- (void)cancelPendingRequest {
[_request cancel];
[_request release], _request = nil;
}
That's all there is to it. The method above will cancel the most recent request, and you'll never hear from it again.

I've followed Matt Wilding's approach listed here, which was very useful, thanks Matt. Unfortunately it didnt quite work for me, so I made some tweaks and now it works... also this revised approach keeps out of the core facebook classes...
//in .h define an FBRequest property
#property (nonatomic, retain) FBRequest * pendingFBRequest;
//in .m when making your request, store it in your FBRequest property
pendingFBRequest = [facebook requestWithGraphPath:#"me/feed"
andParams:params
andHttpMethod:#"POST"
andDelegate:self];
//create a timer for your timeout
pendingFacebookActionTimer = [NSTimer scheduledTimerWithTimeInterval:15.0 target:self selector:#selector(onPendingFacebookActionTimeout) userInfo:nil repeats:NO];
//cancel the action on the timeout's selector method
-(void)onPendingFacebookActionTimeout {
[pendingFBRequest.connection cancel];
}

Updated on 22/April/2012
I update Matt's version with the most up-to-date Facebook iOS SDK. My Project is using ARC, but I include the non-ARC Facebook sources so that I can modify the codes. (Of Course, we need to set the "-fno-objc-arc" flag for Facebook source files). The tricky part is to prevent the memory leak, and I think I am doing it correctly. But When I test it in the instrument, I still see very small amount of memory leak. Fortunately, the details show that they are not related to these codes, so I just assume they are related to the app resource handling.
Here is the code I implemented:
// Add to Facebook.h
- (void)cancelPendingRequest:(FBRequest *)releasingRequest;
And...
// Add to Facebook.m
- (void)cancelPendingRequest:(FBRequest *) releasingRequest{
[releasingRequest.connection cancel];
[releasingRequest removeObserver:self forKeyPath:requestFinishedKeyPath];
[_requests removeObject:releasingRequest];
}
And in your project which uses FBRequestDelegate
// Declare this member or property to the .h file
FBRequest * currentFbRequest;
// Declare this method
-(void)cancelFBRequest;
And ...
// In .m file
AppDelegate * appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// prepare your necessary request data and parameter ...
currentFbRequest = [appDelegate.facebook requestWithGraphPath:#"/me/photos"
andParams:params
andHttpMethod:#"POST"
andDelegate:self];
// Then in the method where you want to cancel
AppDelegate * appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.facebook cancelPendingRequest:currentFbRequest];
currentFbRequest=nil;

For those of us who build the static library and are unable to access the implementation files, a category would be the best way to go.
For those of us who did not build the static library, using a category would be optimal as well because you don't need to modify the existing files.
Here is said category.
// Facebook+Cancel.h
#import "Facebook.h"
#interface Facebook (Facebook_cancel)
- (void)cancelPendingRequest:(FBRequest *)releasingRequest;
- (void)cancelAllRequests;
#end
And then the .m file
// Facebook+Cancel.m
#import "Facebook+Facebook_cancel.h"
#implementation Facebook (Facebook_cancel)
- (void)cancelPendingRequest:(FBRequest *)releasingRequest{
[releasingRequest.connection cancel];
if ([_requests containsObject:releasingRequest]) {
[_requests removeObject:releasingRequest];
[releasingRequest removeObserver:self forKeyPath:#"state"];
}
}
- (void)cancelAllRequests {
for (FBRequest *req in [_requests mutableCopy]) {
[_requests removeObject:req];
[req.connection cancel];
[req removeObserver:self forKeyPath:#"state"];
}
}
#end
For those using any other answer, you are causing a memory leak. The Facebook SDK will warn you through NSLog that you have not removed an observer. The fourth line in the cancelAllRequests method fixes this problem.

Try this instead of using NSTimer:
FBRequest *fbRequest = [facebook requestWithGraphPath:#"me" andDelegate:self];
[self performSelector:#selector(fbRequestTimeout:) withObject:fbRequest afterDelay:30];
- (void)fbRequestTimeout:(FBRequest *)fbRequest
{
[fbRequest.connection cancel];
[fbRequest setDelegate:nil];
}

Since SDK 3.1, it's very easy, as startWithCompletionHandler: returns a FBRequestConnection object, which has a -(void)cancel; method.
For example:
// In interface or .h definitions:
#property (strong, nonatomic) FBRequest *fBRequest;
#property (strong, nonatomic) FBRequestConnection *fbConnection;
// when needed in class (params should be set elsewhere, this is just an example):
self.fBRequest = [[FBRequest alloc] initWithSession:[FBSession activeSession] graphPath:#"me/photos" parameters:params HTTPMethod:#"POST"];
self.fbConnection = [self.fBRequest startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error){
NSLog(#"Publish complete, error: %d", error.code);
}];
// now, to cancel anywhere in the class, just call:
[self.fbConnection cancel];

In FBRequest.h, I've had to add _delegate = nil; because in my case, the request delegate no longer existed (it was dismissed) which caused a crash.

I was having a crash with the previous iOS Facebook SDK which was valid in August 2012 whenever I navigated to another view. My solution is based on #staticfiction response:
Added BOOL viewWillDisappear flag in .h. In -(void) viewWillDisappear: set the flag to YES. Reset flag to NO in -(void) viewDidAppear:
//in .h define an FBRequest property
#property (nonatomic, retain) FBRequest * pendingFBRequest;
/*
* Graph API: Search query to get nearby location.
*/
- (void)apiGraphSearchPlace:(CLLocation *)location {
currentAPICall = kAPIGraphSearchPlace;
NSString *centerLocation = [[NSString alloc] initWithFormat:#"%f,%f",
location.coordinate.latitude,
location.coordinate.longitude];
JMYAppDelegate *delegate = (JMYAppDelegate *)[[UIApplication sharedApplication] delegate];
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
#"place", #"type",
centerLocation, #"center",
#"1000", #"distance",
nil];
[centerLocation release];
pendingFBRequest = [[delegate facebook] requestWithGraphPath:#"search" andParams:params andDelegate:self];
if (viewWillDisappear) {
[pendingFBRequest.connection cancel];
[pendingFBRequest setDelegate:nil];
[self hideActivityIndicator];
}
}

Make a CURL call to this URL
https://graph.facebook.com/REQUEST_ID?method=delete

Related

How to use iOS delegate and callback methods from NMSSH library?

I'm trying to use delegate methods from NMSSH library in iOS but could not get it working. Let's take an example.
CustomViewController.h
#import <UIKit/UIKit.h>
#import <NMSSH/NMSSH.h>
#interface CustomViewController : UIViewController<NMSSHSessionDelegate, NMSSHChannelDelegate>
- (IBAction)connectButton:(UIButton *)sender;
#end
CustomViewController.m
#import "CustomViewController.h"
#implementation CustomViewController
-(void)viewDidLoad{
[super viewDidLoad];
}
- (IBAction)connectButton:(UIButton *)sender {
[self serverConnect:#"10.0.0.1"];
}
-(void)serverConnect:(NSString *)address{
NMSSHSession *session = [NMSSHSession connectToHost:address withUsername:#"username"];
NMSSHChannel *myChannel = [[NMSSHChannel alloc]init];
if (session.isConnected) {
[session authenticateByPassword:#"password"];
if (session.isAuthorized) {
NSLog(#"Authentication succeeded");
[session setDelegate:self];
[myChannel setDelegate:self];
}
}
NSError *error = nil;
//session.channel.requestPty = YES; (tried and later ignored)
NSString *response = [session.channel execute:#"mkdir" error:&error];
NSLog(#"Response from device: %#", response);
}
- (void)session:(NMSSHSession *)session didDisconnectWithError:(NSError *)error{
NSLog(#"log if session disconnects...Delegate method");
}
- (void)channel:(NMSSHChannel *)channel didReadError:(NSString *)error{
NSLog(#"Error received...Delegate method");
}
- (void)channel:(NMSSHChannel *)channel didReadRawData:(NSData *)data{
NSLog(#"Read Raw Data...Delegate method");
}
Connection to the server, sending a single line command and acknowledgement back from the server in Console is OK.
I have decent idea how to pass values from one View Controller to another using delegate (went through few tutorials with practical implementation).
With the same knowledge I am attempting to get response from delegate methods parts of NMSSH library but it's driving me round and round. I've found http://cocoadocs.org/docsets/NMSSH/2.2.1/ pretty nice API of this library but with my limited knowledge of iOS, I'm bit stuck.
Please help me.
My search finally came to an end with NMSSH AsyncAPI (branch) which supports multithreading.

AFNetworking getting response JSON from the wrong endpoint

I am seeing a really weird and random issue in my code that I can't track down. I am getting crashes in my data model init methods when returning from AFNetworking JSON request methods. When the app does crash I am able to step back in the call stack to debug the what the JSON request/response was. The weird part is when I check the URL, request, and resonseJSON. The responseJSON does not match the expected result of the URL/request. It's like I am getting some other API methods call and data. Because the data/JSON is not what I expect the app will crash on model init.
The data I get back is usually different and not always the same. Sometimes the data is from endpoint A and sometimes it is from B, it's never consistent. It does however seem to crash consistently in the same model object.
Request endpoint A data but I get back endpoint B data. When I debug the AFHttpOperation when it crashes I see this is the result. It's almost like 2 calls are getting crossed and is some type of race condition. Below is a sample of my model object, Rest client, and model access layer.
Model Object
#implementation Applications
- (id)initWithData:(NSDictionary *)appData forLocation:(Location *)location inCategory:(Category *)category {
// appData is the JSON returned by The Rest Client and AFNetworking
self = [super init];
DDLogVerbose(#"appData = %#", appData);
if (self) {
_location = location;
_listeners = [NSMutableArray mutableArrayUsingWeakReferences];
_devices = [[NSMutableDictionary alloc] init];
_category = category;
_subscriptions = [Utility sanitizeArray:appData[#"Subscriptions"]];
}
return self;
}
#end
#implementation Location
- (void)refreshApplications {
[[Model shared] appsForLocation:self
success:^(NSObject *obj) {
self.apps = nil; //we have to get our apps again
self.apps = [NSMutableArray array];
NSArray *newApps = (NSArray *) obj;
for (NSDictionary *app in newApps) {
**// This is where it's crashing!**
Applications *newApp = [[Applications alloc] initWithData:app
forLocation:self
inCategory:[[SmartAppCategory alloc] init]];
[self.apps addObject:newApp];
}
[self notifyListeners];
}
error:nil];
}
#end
Rest Client
#interface Rest
+ (Rest *)sharedClient;
- (void)GET:(NSString *)path parameters:(NSDictionary *)params success:(SuccessCallback)sCallback error:(ErrorCallback)eCallback;
#end
#implementation Rest
+ (Rest *)sharedClient {
static dispatch_once_t token;
static Rest *shared = nil;
dispatch_once(&token, ^{
shared = [[Rest alloc] init];
});
return shared;
}
- (id)init {
self = [super init];
if (self) {
[self createClients];
}
return self;
}
- (void)createClients {
// Setup the Secure Client
// Private implementation properties
self.secureClient = [[AFOAuth2Client alloc] initWithBaseURL:baseUrl clientID:OAUTH2_CLIENT_ID secret:OAUTH2_CLIENT_SECRET];
[self.secureClient setParameterEncoding:AFJSONParameterEncoding];
AFOAuthCredential *credential = (AFOAuthCredential *) [NSKeyedUnarchiver unarchiveObjectWithData:[KeyChainStore dataForKey:KEYCHAIN_SETTINGS_AFOAuthCredential]];
if (credential) {
[self.secureClient setAuthorizationHeaderWithToken:credential.accessToken];
}
// Setup the Anonymous Client
self.anonymousClient = [[AFHTTPClient alloc] initWithBaseURL:baseUrl];
[self.anonymousClient setParameterEncoding:AFJSONParameterEncoding];
[self.anonymousClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
}
- (void)GET:(NSString *)path parameters:(NSDictionary *)params success:(SuccessCallback)sCallback error:(ErrorCallback)eCallback {
[_secureClient getPath:path
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject) {
DDLogVerbose(#"Success Path: %# JSON: %#", path, responseObject);
if (sCallback) sCallback(responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[Rest callErrorBlock:eCallback withOperation:operation];
}];
}
#end
Model Access Layer
#interface Model
+ (Model *)shared;
- (void)appsForLocation:(Location *)location success:(SuccessCallback)success error:(ErrorCallback)error;
#end
#implementation Model
- (void)appsForLocation:(Location *)location success:(SuccessCallback)success error:(ErrorCallback)error {
NSString *path = [NSString stringWithFormat:#"/api/locations/%#/apps/", location.locationId];
[[Rest sharedClient] GET:path parameters:nil success:success error:error];
}
#end
A Location is a root object in the application and it will be told to refresh often. Either through UI interaction, events, or data Deserialization the the refreshApplications will execute to get more data from the server. Meanwhile other requests and events are going on in the application to get and send data to the API is JSON. Some of these GET calls to other endpoints seem to be messing with the response data.
Questions
How could this be happening with AFNetworking?
Am I being too quick to blame AFNetowrking and should I be looking for other places in my system that could be crossing the responses? I do have a load balanced backend hosted at Amazon.
Is this an endpoint issue?
How can I better debug and reproduce this issue? It only comes up at random times and is very hard to replicate. I have to continually run and re-run the application in hopes that it is crash.
Are there any advanced debugging techniques that I can use to back trace this call/crash using xcode?
I recommend that you use Charles proxy to double-check that the data you're receiving is correct. There's a trial version available that works identically to the registered version for 30 days. My first guess is that there's either some sort of buggy cache layer between you and your server, or your server is buggy. An HTTP proxy like Charles will allow you to confirm or reject this hypothesis.
This page explains how to set up Charles to proxy non-HTTPS connections from iOS devices.
To debug non-HTTPS as well as HTTPS Traffic use the mitmproxy
It allows you to inspect all packages and also resend them and much more.
With this you can check what really happens and if the backend is the problem or if AFNetworking has a Bug.
And as a cool side effect mitmproxy is totally free and Open-Sourced under the MIT Licensed.
On their website you will find some handy tutorials specific for iOS.

iOS Twitter Reverse OAuth

I have been pouring over the internet for days now trying to figure out how to implement this.
I need to request the access token and secret from twitter in order to pass this to a server that will process the users tweets for my application.
I have been following this link https://dev.twitter.com/docs/ios/using-reverse-auth
The problem is step 1. They dont give you an example of step 1.
Here is my code:
NSURL *url = [NSURL URLWithString:TW_OAUTH_URL_REQUEST_TOKEN];
NSDictionary *parameters = #{TW_X_AUTH_MODE_KEY:TW_X_AUTH_MODE_REVERSE_AUTH};
SLRequest *getTwitterAuth = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:url parameters:parameters];
// Assume that we stored the result of Step 1 into a var 'resultOfStep1'
NSString *S = resultOfStep1;
NSDictionary *step2Params = [[NSMutableDictionary alloc] init];
[step2Params setValue:#"kfLxMJsk7fqIuy8URhleFg" forKey:#"x_reverse_auth_target"];
[step2Params setValue:S forKey:#"x_reverse_auth_parameters"];
NSURL *url2 = [NSURL URLWithString:#"https://api.twitter.com/oauth/access_token"];
SLRequest *stepTwoRequest =
[SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:url2 parameters:step2Params];
// You *MUST* keep the ACAccountStore alive for as long as you need an ACAccount instance
// See WWDC 2011 Session 124 for more info.
self.accountStore = [[ACAccountStore alloc] init];
// We only want to receive Twitter accounts
ACAccountType *twitterType =
[self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Obtain the user's permission to access the store
[self.accountStore requestAccessToAccountsWithType:twitterType
withCompletionHandler:^(BOOL granted, NSError *error) {
if (!granted) {
// handle this scenario gracefully
} else {
// obtain all the local account instances
NSArray *accounts =
[self.accountStore accountsWithAccountType:twitterType];
// for simplicity, we will choose the first account returned - in your app,
// you should ensure that the user chooses the correct Twitter account
// to use with your application. DO NOT FORGET THIS STEP.
[stepTwoRequest setAccount:[accounts objectAtIndex:0]];
// execute the request
[stepTwoRequest performRequestWithHandler:
^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSString *responseStr =
[[NSString alloc] initWithData:responseData
encoding:NSUTF8StringEncoding];
// see below for an example response
NSLog(#"The user's info for your server:\n%#", responseStr);
}];
}
}];
I have been trying to figure out how I process the SLRequest in oder to pass it to step 2 from the twitter docs.
I have also used this here: https://github.com/seancook/TWReverseAuthExample
This code is great but very complex. Any help would be greatly appreciated! Thanks!
The reason step one doesn't have any code is that they assume you will do this on your server or before hand or something like that. Basically you need to generate a key that your app will use to convert iOS tokens to normal tokens.
There is a script that will make the request for you here: http://www.ananseproductions.com/twitter-reverse-auth-headaches/ Its written in ruby so you could use something similar if you have a ruby server.
Personally I would have my app request this token from my server, then make the request to twitter, then post the new token back to my server.
Here is a class to help accomplish just this with a single method call that returns a dictionary with the token and token secret.
https://github.com/kbegeman/Twitter-Reverse-Auth
Hope this helps others out!
As of this code https://github.com/seancook/TWReverseAuthExample , it's fairly simple to implement in your own application. I prefer to create reusable classes, so I don't have to implement the same code multiple times. Normally you would create some singleton and work with it on the following tutorial. However the point of this instruction is not to teach you how to create singletons, so for the simplicity sake, we will use AppDelegate.h/m which is easily accessible from all over the application.
All you have to do is the following:
Open yours and Sean Cook's project (the one which URL is above)
Drag and copy Source->Vendor->ABOauthCore group into your project
Select TWAPIManager.h/m, TWSignedRequest.h/m and copy them into your project
Add the below code into your AppDelegate.h file
#property (nonatomic, strong) ACAccountStore* store;
#property (nonatomic, strong) TWAPIManager *apiManager;
#property (nonatomic, strong) NSArray *accounts;
-(void)storeAccountWithAccessToken:(NSString *)token secret:(NSString *)secret;
-(void)performReverseAuth:(id)sender inView:(UIView*)viewToDisplaySheet;
-(void)_refreshTwitterAccounts;
Now paste the following methods into your AppDelegate.m file
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex;
-(void)_refreshTwitterAccounts;
-(void)_obtainAccessToAccountsWithBlock:(void (^)(BOOL))block;
-(void)performReverseAuth:(id)sender inView:(UIView*)viewToDisplaySheet;
In some initialization method of your file, or as of this example in: `application: didFinishLaunchingWithOptions' paste the following code:
_store = [[ACAccountStore alloc] init];
_apiManager = [[TWAPIManager alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(_refreshTwitterAccounts) name:ACAccountStoreDidChangeNotification object:nil];
Remember to remove observer using the following code. Paste it in AppDelegate.m:
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Open your app-Info.plist file and add 2 string keys. Take their values from: https://apps.twitter.com/
TWITTER_CONSUMER_KEY
TWITTER_CONSUMER_SECRET
In the View Controller that you want to use to implement twitter features, in the viewDidLoad method, add the following code:
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate _refreshTwitterAccounts];
OK, finally you are ready to start the whole machine. In the View Controller that you want to use to implement twitter features, create UIButton called _reverseAuthBtn and create an IBAction to it. Then in your IBAction paste the following code:
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate performReverseAuth:sender inView:self.view];
Whew, I guess that's it! If I haven't forgotten about anything, you have got Twitter Reverse Oauth implementation, and if you want to use it in multiple view controllers, all you have to do is do steps 1-8, and then paste the code from the steps 9 and 10 into your view controller.
Best regards!
Use this lib, it works perfectly!
https://github.com/nst/STTwitter
Info how to implement: https://github.com/nst/STTwitter#reverse-authentication
:)

Parse Facebook Dialog Delegate methods not being called

I'm using the Parse SDK to implement a Facebook apprequests dialog in my app. For all intents and purposes it just wraps the Facebook SDK, prefixing Facebook methods with PF_.
I use this code to prepare and raise the dialog:
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSString stringWithFormat:#"I just challenged you to a game!"], #"message",
[friendIds componentsJoinedByString:#"," ], #"to",
nil];
PF_FBSession *session = [PFFacebookUtils session];
PF_Facebook *facebook = [[PF_Facebook alloc] initWithAppId:session.appID andDelegate:nil];
facebook.accessToken = session.accessToken;
facebook.expirationDate = session.expirationDate;
[facebook dialog:#"apprequests" andParams:params andDelegate:self];
This works well, I'm getting the dialog, I'm able to invite friends to play with the app.
The problem is that the delegate methods are not being called, even though I've set the view controller as a PF_FBDialogDelegate:
#interface ChallengeFriendsViewController : UIViewController <UITableViewDelegate, PF_FBDialogDelegate> {
NSArray *facebookFriends;
NSMutableArray *selectedFriends;
}
These are some of the delegate methods I'm talking about:
- (void)dialog:(PF_FBDialog *)dialog didFailWithError:(NSError *)error {
NSLog(#"Error in Dialog: %#", error);
}
- (void)dialogDidNotCompleteWithUrl:(NSURL *)url {
NSLog(#"Failure on Facebook server side");
}
- (void)dialogCompleteWithUrl:(NSURL *)url {
NSLog(#"Did complete with URL");
[self.navigationController popViewControllerAnimated:YES];
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
}
- (void)dialogDidNotComplete:(PF_FBDialog *)dialog {
NSLog(#"Cancelled");
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
}
Without these methods being called I'm really not able to handle the sharing in an intuitive way. I'm stumped as to why they wouldn't be called and feel I've tried everything. Any pointers to where I'm going wrong?
In your code you have
PF_Facebook *facebook = [[PF_Facebook alloc] initWithAppId:session.appID andDelegate:nil];
Since you set a nil delegate, I would expect the delegate methods are never called, as you describe.
Could you change this to
PF_Facebook *facebook = [[PF_Facebook alloc] initWithAppId:session.appID andDelegate:self];
That is assuming that you put the delegate methods in the same class.
I finally got around the problem by using the facebook instance provided by PFFacebookUtils. This is deprecated but appears to be the only way to make it call the correct delegate methods at the moment.
Replaced:
PF_Facebook *facebook = [[PF_Facebook alloc] initWithAppId:session.appID andDelegate:nil];
With:
PF_Facebook *facebook = [PFFacebookUtils facebook];
Thanks to JP and Aromal for your input.

Liking a Facebook page from native iOS application

I am attempting to LIKE a business Facebook Like page (facebook.com/[LikePage]) from my native iOS application. I have used FB iOS SDK for login/logout purpose.
I have implemented the LIKE button similar to http://angelolloqui.com/blog/10-Facebook-Like-Button-on-iOS, which is an implementation of the social plugin on a webview. I did so, because of my understanding that to implement a custom LIKE button, I need to use the Built-in Like provided by FB which in turn would require that my actions be approved by FB.
However, it was brought to my notice that the social plugin implementation cannot be used in an iOS native application and can be used only in a mobile web app.
So, here are my questions :-
Is it true that native iOS applications cannot use the social plugin provided by facebook to like a Facebook Page ?
Is it true that in order to build a custom Like button and not the plugin, I need to use the Built-in Likes provided by Facebook, which in turn would require approval of my action types ? (In my case, LIKE a Page)
All I need is some concrete documentation which clearly lets me know which is the best way forward.
FYI, the business page URL's that need to be LIKEd, come dynamically from server.
Thanks in Advance.
There's no API or automatic method to like facebook pages. The built-in open graph action, once approved, allows you to like other URLs which have Open Graph meta tags on them but not Facebook Pages.
The Like button plugin should work in a webview as far as I know.
You can just use a regular NSURLRequest to like the page, or use another library to make a post call to: https://graph.facebook.com/{PAGE_OR_OBJECT_ID}/likes. Make sure you add the acces_token as a parameter.
I use AFNetworking to post requests:
NSURL *baseURL = [NSURL URLWithString:#"https://graph.facebook.com/"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
NSString *link = [NSString stringWithFormat:#"/%#/likes", myObjectID];
NSDictionary *params = #{#"access_token" : FBSession.activeSession.accessToken};
[httpClient postPath:link parameters:params success:^(AFHTTPRequestOperation *op, id result) {
NSLog(#"result %#", result);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error %#", error);
}];
An update to this post:-
"With the release of the Facebook SDK version 4.28.0, the Like Button for iOS is deprecated. It will be supported until February 5, 2018."
https://developers.facebook.com/docs/sharing/ios/like-button
try this Code:
I think this will surely help you:
Fb like Widget can be embedded in our application. You just have to add a webView and get the Fb Like Widget html code/URL here.
in ViewController.h where you want to add fb like button:
#import <UIKit/UIKit.h>
#interface TestViewController : UIViewController <UIWebViewDelegate>
#property (strong, nonatomic) UIWebView * fbLikeWebView;
-(void)embedFBLikeButton;
#end
in TestViewController.m
#import "AboutUsViewController.h"
#implementation AboutUsViewController
#synthesize fbLikeWebView = _fbLikeWebView;
- (void)viewDidLoad
{
[super viewDidLoad];
//Add this code for FbLike Webview
self.fbLikeWebView = [[UIWebView alloc] initWithFrame: CGRectMake(100.0, 50.0, 55.0, 70.0)];
_fbLikeWebView.opaque = NO;
_fbLikeWebView.backgroundColor = [UIColor clearColor];
_fbLikeWebView.delegate = self;
[self.view addSubview:_fbLikeWebView];
for (UIScrollView *subview in _fbLikeWebView.subviews)
{
if ([subview isKindOfClass:[UIScrollView class]]) {
subview.scrollEnabled = NO;
subview.bounces = NO;
}
}
}
then in ViewWillAppear method call the enbeddFBLikeButton Method to add the fbLike button wigdet on web view:
-(void)viewWillAppear:(BOOL)animated
{
[self embedFBLikeButton];
[_fbLikeWebView reload];
}
-(void)embedFBLikeButton
{
NSString *facebookUrl = //here paste the url you get from fb developer link above;
[self.fbLikeWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:facebookUrl]]];
}
You conform to UIWebViewDelegate now its turn to defining th edelegate method here:
#pragma mark - WebView Delgate Methods
- (BOOL)webView:(UIWebView *)webview shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.lastPathComponent isEqualToString:#"login.php"])
{
[self login];
return NO;
}
return YES;
}
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
[_fbLikeWebView stopLoading];
}
This method for login the user to facebook Account:
- (void)login
{
[FBSession setActiveSession: [[FBSession alloc] initWithPermissions:#[#"publish_actions", #"publish_stream", #"user_photos"]]];
[[FBSession activeSession] openWithBehavior: FBSessionLoginBehaviorForcingWebView completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
switch (status) {
case FBSessionStateOpen:
// call the legacy session delegate
//Now the session is open do corresponding UI changes
if (session.isOpen) {
FBRequest *me = [FBRequest requestForMe];
[me startWithCompletionHandler: ^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *my,
NSError *error) {
if (!my) {
NSLog(#"Facebook error:\n%#", error.description);
[[[UIAlertView alloc] initWithTitle: #"Error"
message: #"Facebook Login error."
delegate: self
cancelButtonTitle: #"Ok"
otherButtonTitles: nil, nil] show];
return;
}
}];
[_fbLikeWebView reload];
[[[UIAlertView alloc] initWithTitle: #""
message: #"Successfully Login. Please click on like button"
delegate: self
cancelButtonTitle: #"Ok"
otherButtonTitles: nil, nil] show];
}
break;
case FBSessionStateClosedLoginFailed:
{
[_fbLikeWebView reload];
}
break;
default:
break; // so we do nothing in response to those state transitions
}
}];
}
Enjoy Coding!!!

Resources