I've been playing a bit with the new Social.Framework and in particular with SLRequest, both available on iOS 6 and upwards. Thing is, I got really surprised by a crash I've been getting when trying to post such request.
I've been getting the crash with both Facebook and Twitter accounts, so that's why I knew it wasn't related to any particular issue with one of them. It had to be related to the ACAccount object, which I'm getting in this way:
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"6.0")) {
//iOS 6
if (_twitterEnabled) {
if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) {
//Set up account
[accountStore requestAccessToAccountsWithType:accountTypeTwitter options:nil completion:^(BOOL granted, NSError *error) {
if (granted && !error) {
//Get account and get ready to post.
NSArray *arrayOfAccounts = [accountStore accountsWithAccountType:accountTypeTwitter];
if ([arrayOfAccounts count] > 0) {
_twitterAccount = [arrayOfAccounts lastObject];
}
}
}];
}
}
if (!_facebookEnabled) {
ACAccountType *accountTypeFacebook = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) {
NSArray *permissions = #[#"user_about_me",#"user_likes",#"email"];
NSMutableDictionary *options = [NSMutableDictionary dictionaryWithObjectsAndKeys:kFacebookAppIDString, ACFacebookAppIdKey, permissions, ACFacebookPermissionsKey, ACFacebookAudienceFriends, ACFacebookAudienceKey, nil];
[accountStore requestAccessToAccountsWithType:accountTypeFacebook options:options completion:^(BOOL granted, NSError *error) {
if (granted && !error) {
[options setObject:#[#"publish_stream",#"publish_actions"] forKey:ACFacebookPermissionsKey];
[accountStore requestAccessToAccountsWithType:accountTypeFacebook options:options completion:^(BOOL granted, NSError *error) {
if (granted && !error) {
NSArray *arrayOfAccounts = [accountStore accountsWithAccountType:accountTypeFacebook];
if ([arrayOfAccounts count] > 0) {
_fbAccount = [arrayOfAccounts lastObject];
}
}
}];
}
}];
}
}
}
Both _twitterAccount and _fbAccount are ACAccount objects in which I store the relevant account when retrieved from the Account Store.
The problem came when later I tried to use such objects (I'll just post the twitter method for brevity's sake):
NSDictionary *twitterMsg = #{#"status" : message};
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"6.0")) {
SLRequest *postRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:kTwitterUpdateStatusURL parameters:twitterMsg];
[postRequest setAccount:_twitterAccount];
[postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSLog(#"Twitter HTTP response: %d", [urlResponse statusCode]);
}];
}
When calling setAccount: on postRequest I was getting an exception with the message: "Invalid account type for this request", which was obviously false. I also tried to debug the code and strangely the accountType on _twitterAccount was set to nil right before being sent to the ACAccount object. More strangely, if I put
NSLog(#"%#",_twitterAccount);
right under
_twitterAccount = [arrayOfAccounts lastObject]
on the first section of code, it works with no problem.
I've reviewed my code and I don't think I'm doing anything wrong so that I think it can be a bug on the framework? It looks like the ACAccountType is being released when it shouldn't, but I wanted to check if anyone of you could see anything wrong with my code that was provoking it and/or find an explanation for the issue, which I'm unable to solve by myself.
Thank you
UPDATE
It seems some other people have the same issue, I'm accepting one of the answers because it actually solves the issue, but I'll be looking forward to anyone that can find an explanation for the issue.
I was also getting "Invalid account type for this request" when using SLRequest on accounts retrieved via both of the following
ACAccountStore *account_store = [[ACAccountStore alloc] init];
ACAccountType *account_type_twitter = [account_store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// A.
NSArray *accounts = [account_store accountsWithAccountType:account_type_twitter];
// B.
NSString *account_id = #"id from doing account.identifier on one of the above";
ACAccount *account = [account_store accountWithIdentifier:account_id];
NSLog on the account had everything populated as expected but the type was set as null. Guessing this is a bug with Apple's SDK. Doing the following fixed the accounts for me so I could use them with SLRequest:
ACAccountType *account_type_twitter = [account_store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
account.accountType = account_type_twitter;
You have to retain ACAccountStore:
#property (nonatomic, strong) ACAccountStore *accountStore;
The ACAccount documentation never states specifically that you must retain ACAccountStore, but it does state that
"To create and retrieve accounts from the Accounts database, you must
create an ACAccountStore object. Each ACAccount object belongs to a
single ACAccountStore object."
When you call:
NSArray *accounts = [accountStore accountsWithAccountType:accountType]
Those accountStore objects in the array don't necessarily have all of their properties fetched from the database. Some properties (like accountType) are only retrieved from the Accounts database if needed. This caused the strange behavior that you saw when you logged the account and everything magically worked. When you logged the account, the ACAccountStore object was still in memory and the logging caused the retrieval of the AccountType property. At that point, the AccountType was retained with the ACAccount and everything was able to work later even after ACAccountStore was released.
Are you retaining the ACAccountStore you used to fetch the arraryOfAccounts?
I use this code without problem. (Twitter only)
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error) {
if (!granted) {
NSLog(#"Access denied.");
}
else {
NSArray *accounts = [accountStore accountsWithAccountType:accountType];
if (accounts.count > 0) {
twitterAccount = [accounts objectAtIndex:0];
}
}
}];
I think something about accountStore init issue?
Twitter's official reference is here.
Twitter: API requests with TWRequest
Related
I am trying to use native (iOS 6-7x) libraries to authorize a user with Facebook from my app. I would like to pass the auth token to my server when login is successful.
The code below works fine except when the user has not set up their Facebook account in the OS. I get the following error in this case:
Error Domain=com.apple.accounts Code=6 "The operation couldn’t be completed. (com.apple.accounts error 6.)
-(void) initFacebookLogin
{
LRAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
if (appDelegate.accountStore == nil)
appDelegate.accountStore = [[ACAccountStore alloc] init];
__block ACAccount *facebookAccount = nil;
ACAccountType *facebookAccountType = [appDelegate.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
NSArray * permissions = #[#"publish_stream", #"publish_actions", #"email"];
NSMutableDictionary *options = [[NSMutableDictionary alloc] initWithObjectsAndKeys:FACEBOOK_APP_ID, ACFacebookAppIdKey, permissions, ACFacebookPermissionsKey, ACFacebookAudienceOnlyMe, ACFacebookAudienceKey, nil];
[appDelegate.accountStore requestAccessToAccountsWithType:facebookAccountType
options: options
completion: ^(BOOL granted, NSError *error) {
if ( granted )
{
NSArray *accounts = [appDelegate.accountStore accountsWithAccountType:facebookAccountType];
facebookAccount = [accounts lastObject];
ACAccountCredential* accountCredential = [facebookAccount credential];
NSString* token = [accountCredential oauthToken];
NSLog( #"token=%#", token );
}
else {
// WHAT DO I DO HERE???
// Error Domain=com.apple.accounts Code=6 "The operation couldn’t be completed. (com.apple.accounts error 6.)"
NSLog(#"%#", [error description]);
}
}];
}
Do I still need to use the Facebook SDK to ask the user to log in? Is there another iOS native library I could use to prompt the user to set up Facebook access in iOS?
OR, is there a better way to do streamlined Facebook auth (not asking the user to log in if they have already done so in the OS)?
You should take a look at Facebook docs here (there's a simple login example):
https://developers.facebook.com/docs/ios/ios-sdk-tutorial/authenticate/
In my opinion, I would use the Facebook SDK to perform the login, you can do something like this:
/* Constants */
#define K_FACEBOOK_REQUEST_CREDENTIALSURL #"me/?fields=id,location,name,username,bio,picture,gender,website,birthday,email"
#define K_FACEBOOK_PERMISSIONS #[#"user_about_me",#"user_birthday",#"email"]
/* Facebook Keys */
#define K_FACEBOOK_REQUEST_ID #"id"
#define K_FACEBOOK_REQUEST_USERNAME #"username"
#define K_FACEBOOK_REQUEST_LOCATION #"location"
#define K_FACEBOOK_REQUEST_FULLNAME #"name"
#define K_FACEBOOK_REQUEST_BIO #"bio"
#define K_FACEBOOK_REQUEST_WEBSITE #"website"
#define K_FACEBOOK_REQUEST_EMAIL #"email"
#define K_FACEBOOK_REQUEST_BIRTHDAY #"birthday"
#define K_FACEBOOK_REQUEST_GENDER #"gender"
- (void)initWithLoginFacebook
{
[PFFacebookUtils logInWithPermissions:K_FACEBOOK_PERMISSIONS block:^(PFUser *user, NSError *error) {
if (!user) {
if (!error)
NSLog("ERROR_CAUSE_AUTH_USERCANCELLED");
else
NSLog("ERROR_CAUSE_AUTH_FAILEDLOGIN");
} else if (user.isNew) {
FBRequest *request = [FBRequest requestForMe];
[request startWithCompletionHandler:^(FBRequestConnection *connection,id result,NSError *error) {
if (error)
NSLog("ERROR_CAUSE_AUTH_FAILEDLOGIN");
else
[self requestLoginFacebook:result];
}];
} else
NSLog("User logged and not new!");
}];
}
- (void)requestLoginFacebook:(id)result
{
NSDictionary *userData = (NSDictionary *)result;
// Do something with the data... For example
NSString *facebookId = userData[K_FACEBOOK_REQUEST_ID];
NSString *name = userData[K_FACEBOOK_REQUEST_FULLNAME];
NSString *username = [userData[K_FACEBOOK_REQUEST_USERNAME] lowercaseString];
NSString *bio = userData[K_FACEBOOK_REQUEST_BIO];
NSString *website = userData[K_FACEBOOK_REQUEST_WEBSITE];
NSString *location = userData[K_FACEBOOK_REQUEST_LOCATION][#"name"];
NSString *gender = userData[K_FACEBOOK_REQUEST_GENDER];
NSString *birthday = userData[K_FACEBOOK_REQUEST_BIRTHDAY];
NSString *email = userData[K_FACEBOOK_REQUEST_EMAIL];
NSString *profileImage = [NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?width=180&height=180",userData[#"id"]];
}
You have to configure the Facebook SDK in the appDelegate and in the .plist file.
Your code only will work fine if the user has a Facebook account set in the device Settings.
I.
I have created titter login in my iphone application,here i want to send my access token and secret key to server for auto sharing functionality, i can't get my access token and sekret key ,please help me out thanks in advance.
This is my code:
ACAccountType *twitterType = [store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[store requestAccessToAccountsWithType:twitterType
withCompletionHandler:^(BOOL granted, NSError *error){
if(!granted){
NSLog(#"%#",[error localizedDescription]);
}
NSLog(#"store %#",store);
}];
if([[store accounts] count] > 0)
{
NSLog(#"TWEET");
self.hasTwitterAccountAccess = YES;
}
else
{
NSLog(#"newlog");
self.hasTwitterAccountAccess = NO;
}
if(self.hasTwitterAccountAccess){
NSLog(#"login");
// If we have twitter access, refresh the table view
ACAccountType *twitterType = [store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
}
You have to use Reverse OAuth: https://dev.twitter.com/docs/ios/using-reverse-auth
Let me make this clear. I am NOT using the Facebook SDK. I'm using iOS SDK's Social.framework, and ACAccountStore to access Facebook accounts, and post with it/them.
I use the same code to post on Twitter. It works 100%. But for some reason regardless of what I do for Facebook integration, I get a "400" error when I try to post.
My method is:
ACAccountStore *account = [[ACAccountStore alloc] init];
ACAccountType *facebookAccountType = [account accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
// Specify App ID and permissions
NSDictionary *options = #{ ACFacebookAppIdKey: #"MY_APP_ID",ACFacebookPermissionsKey: #[#"publish_stream", #"publish_actions"],ACFacebookAudienceKey: ACFacebookAudienceFriends };
[account requestAccessToAccountsWithType:facebookAccountType options:options
completion:^(BOOL granted, NSError *error)
{
if (granted == YES)
{
NSDictionary *parameters = #{#"message": string999};
NSURL *feedURL = [NSURL URLWithString:#"https://graph.facebook.com/me/feed"];
SLRequest *feedRequest = [SLRequest
requestForServiceType:SLServiceTypeFacebook
requestMethod:SLRequestMethodPOST
URL:feedURL
parameters:parameters];
acct.accountType = facebookAccountType;
// Post the request
[feedRequest setAccount:acct];
// Block handler to manage the response
[feedRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
if (granted && error == nil) {
} else {
NSLog(#"Facebook response, HTTP response: %i %#", [urlResponse statusCode], [error description]);
[self closeShareMenu];
}
}];
}
}
I don't know where I'm going wrong! It's so annoying! I've set up my app correctly in Facebook Developers and all! Please help -_-'
Following up to the chat session held between #fguchelaar and yours truly yesterday; I was able to ascertain the following solution for this issue.
Add the following in your iOS completion handler:
NSString *temp = [[NSString alloc] initWithData:data encoding: NSUTF8StringEncoding];
NSLog(temp);
//'data' is your 'responseData' (or another object name) that you declare in your completion handler.
This will allow you to see the exact cause of the issue printed to the Debug Console. Now depending on the issue presented, you'll need to grab a Facebook account from the Array of Accounts generated when you call this handler in the iPhone SDK. Not at any prior stage whatsoever, as the Access Token will likely expire and give you this '400' error.
In my case; the error printed was: error:{'400' A valid access token is required… which vastly annoyed me as my prior method to access and check the current Twitter account was working perfectly. And my theory was that it should work just as well for Facebook. Why should the access token be instantaneously revoked if I'm grabbing the account a split second before?
The way I solved my issue (depending on the reason for your error the answer can vary) was to use a for loop to check the newly created array of accounts, with the sole purpose of finding the account there with the same identifier string as the one I saved into NSData/NSKeyedArchiver.
for(ACAccount *a in arrayOfAccounts) {
if([a.identifier isEqualToString:storedAccount.identifier]) {
//set the account to be used
accountToBeUsed = a;
//don't forget to break the For loop once you have your result.
break;
} else {
//This else{} block is not strictly necessary, but here you could set an account if no account was found with a matching identifier.
}
}
For it to work, it's recommended to declare an ACAccount object in your View Controller's .h file, add a #property and #synthesize it, so it can be assigned within the for loop and used after the break; statement.
This effectively solved my whole issue with the '400' error. It was inexplicably frustrating for about six hours of my day, so I hope that my explanation helps anybody who happens to stumble across this issue, and my question here on Stack Overflow :)
Regards,
cocotutch
Can you help me to understand when should I use UIActivityViewController. I have a button which shares common information about my app (something like "I like this app" with link and image). My old code was:
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:picture forKey:#"picture"];
[params setObject:link forKey:#"link"];
[params setObject:#"I like MY app!" forKey:#"caption"];
[params setObject:#"I am now using MY iPhone app." forKey:#"description"];
[params setObject:linkToTheIcon forKey:#"icon"];
[params setObject:#"including link" forKey:#"type"];
[[FacebookConnection instance] feedLink:params andDelegate:self];
Now I want to use UIActivityViewController but I'm a bit confused how to pass all those parameters to it. Or should I do things in other way?
ADDED:
So I understood that I need silent posting procedure.
Could you please guide me through silent post procedure using iOS 6 features (e.d. using built-in FB account). For now I can't understand how to check if FB account exists on the device and if it is not how to prompt to create it? There is a method in ACAccount store class – requestAccessToAccountsWithType:options:completion: to access an account. But If an account does not exists it returns an error. Many thanks in advance.
In your situation it seems obvious that you should not use UIActivityViewController because you want to post on Facebook and not on twitter or anywhere else, right?
Firstly you need to get access to user's account. You do this like this:
-(void)requestBasicPermissionsForFacebookAccount {
ACAccountType * facebookAccountType = [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
NSArray * permissions = #[#"email"];
NSDictionary * options = #{ACFacebookAppIdKey : kFacebookAppId, ACFacebookPermissionsKey : permissions, ACFacebookAudienceKey : ACFacebookAudienceEveryone};
FacebookAccountManager * fbMgr = [[FacebookAccountManager alloc] init];
[self.accountStore requestAccessToAccountsWithType:facebookAccountType options:options completion:^(BOOL granted, NSError *error) {
if (granted) {
NSArray * accounts = [self.accountStore accountsWithAccountType:facebookAccountType];
fbMgr.account = [accounts lastObject];
fbMgr.isBasicPermissionsGranted = YES;
[self.accountManagers addObject:fbMgr];
NSLog(#"granted!");
}
else {
fbMgr.account = nil;
fbMgr.isBasicPermissionsGranted = NO;
switch ([error code]) {
case 1:
[self showErrorAlertWithMessage:#"Unknown error occured, try again later!"];
break;
case 3:
[self showErrorAlertWithMessage:#"Authentication failed, try again later!"];
break;
case 6:
[self showErrorAlertWithMessage:#"Facebook account does not exists. Please create it in Settings and come back!"];
break;
case 7:
[self showErrorAlertWithMessage:#"Permission request failed. You won't be able to share information to Facebook"];
break;
default:
break;
}
NSLog(#"error is: %#", error);
}
}];
}
If an account does not exists you should prompt user to create it in settings and then try to to obtain basic permissions again.
You first need to subclass UIActivity.
Then you need to override certain methods, including activityImage for setting the icon and performActivity for performing the action .
If instead of performing the action silently, you first need further user interaction and info for your custom activity (e.g., like the Twitter post for the standard UIActivity), you should override activityViewController rather than performActivity.
After you have subclassed UIActivity (as, e.g, MyActivity), you should create an instance of MyActivity and make it an element of the applicationActivities array that you pass to initWithActivityItems:applicationActivities:.
Have a look at the documentation for UIActivity for exactly what you need to override when subclassing and for icon requirements.
Hope this helps a little
I have the following method called when my UITableView reloads:
-(NSArray *)theAccounts {
if (__theAccounts != nil) {
return __theAccounts;
}
// Create an account store object.
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
// Create an account type that ensures Twitter accounts are retrieved.
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Request access from the user to use their Twitter accounts.
[accountStore requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
if(granted) {
// Get the list of Twitter accounts.
NSArray *accountsArray = [[NSArray alloc] initWithArray:[accountStore accountsWithAccountType:accountType]];
self.theAccounts = accountsArray;
}
}];
return __theAccounts;
}
Setter Methods in .h:
#property (strong, nonatomic) NSArray *theAccounts;
and in the .m:
#synthesize theAccounts = __theAccounts;
I would like to be able to effectively empty self.theAccounts and reload. So I created a resync method, but it never returns any values after I reload the table:
-(void)resyncAccounts {
self.theAccounts = nil;
[self.tableView reloadData];
}
I am using ARC on iOS 5 SDK. Could this be an issue? I've done similar before with fetchedResultsController and had no issues, but that was not ARC. Worth noting that it does return data the first time it is called, and returns __TheAccounts after that, until I try to -(void)resyncAccounts{}.
Why don't you reload the tableView after setting "theAccounts" in the completion handler? Also, shouldn't you call "self.theAccounts" after setting it to nil?
in the getter, you are returning nil the first time because the block has not yet executed