iOS 5 : Twitter Framework – Part 2 – Call Twitter API with TWRequest

Mon, Oct 24

In the previous post on iOS 5 and the new Twitter framework, I walked through how to use TWTweetComposeViewController to display a pre-built controller for easily integrating and posting to Twitter.

In this post I will show you how to use the TWRequest object to create an HTTP request, and in turn, sending and processing the results of the request. There are three primary components of a Twitter request: the URL of the desired Twitter service you are after, the type of HTTP request (GET, POST or DELETE) and any query parameters (required or optional) of the service requested. It’s also worth noting, using when using TWRequest user authentication is handled for you.

Twitter Search

By looking at the Twitter API I found how to properly setup a search request. Below I do a search for the string “iOS 5″, which looks like this when url-encoded: “q=iOS%205″. Check out the API for more information about the additional parameters.

#import <Twitter/Twitter.h>
 
...
 
// Do a simple search, using the Twitter API
TWRequest *request = [[TWRequest alloc] initWithURL:[NSURL URLWithString:
   @"http://search.twitter.com/search.json?q=iOS%205&rpp=5&with_twitter_user_id=true&result_type=recent"] 
   parameters:nil requestMethod:TWRequestMethodGET];
 
// Notice this is a block, it is the handler to process the response
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
  if ([urlResponse statusCode] == 200) 
  {
    // The response from Twitter is in JSON format
    // Move the response into a dictionary and print
    NSError *error;        
    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
    NSLog(@"Twitter response: %@", dict);                           
  }
  else
    NSLog(@"Twitter error, HTTP response: %i", [urlResponse statusCode]);
}];

And here is what a partial response, in JSON, looks like:

Twitter response: {
    "completed_in" = "0.179";
    "max_id" = 128283809723592705;
    "max_id_str" = 128283809723592705;
    "next_page" = "?page=2&max_id=128283809723592705&q=ios%205&rpp=5&with_twitter_user_id=1";
    page = 1;
    query = "ios+5";
    "refresh_url" = "?since_id=128283809723592705&q=ios%205&with_twitter_user_id=1";
    results =     (
                {
            "created_at" = "Mon, 24 Oct 2011 01:36:58 +0000";
            "from_user" = XXXXXXXXXXX;
            "from_user_id" = 14379847;
            "from_user_id_str" = 14379847;
            geo = "<null>";
            id = 128283809723592705;
            "id_str" = 128283809723592705;
            "iso_language_code" = en;
            metadata =             {
                "result_type" = recent;
            };
  ...
}

You can use the above query even if there is no Twitter account setup on the device – for example, copy/paste the URL http://search.twitter.com/search.json?q=iOS%205&rpp=5&with_twitter_user_id=true&result_type=recent into a browser to view the search request.

Twitter Post

Let’s look at how to create a custom tweet, that is, post to Twitter without using TWTweetComposeViewController.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#import <Accounts/Accounts.h>
#import <Twitter/Twitter.h>
 
...
 
if ([TWTweetComposeViewController canSendTweet]) 
{
  // Create account store, followed by a twitter account identifier
  // At this point, twitter is the only account type available
  ACAccountStore *account = [[ACAccountStore alloc] init];
  ACAccountType *accountType = [account accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
 
  // Request access from the user to access their Twitter account
  [account requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) 
  {
    // Did user allow us access?
    if (granted == YES)
    {
      // Populate array with all available Twitter accounts
      NSArray *arrayOfAccounts = [account accountsWithAccountType:accountType];
 
      // Sanity check
      if ([arrayOfAccounts count] > 0) 
      {
        // Keep it simple, use the first account available
        ACAccount *acct = [arrayOfAccounts objectAtIndex:0];
 
        // Build a twitter request
        TWRequest *postRequest = [[TWRequest alloc] initWithURL:
                                  [NSURL URLWithString:@"http://api.twitter.com/1/statuses/update.json"] 
                                  parameters:[NSDictionary dictionaryWithObject:@"Custom tweet from iOS 5 Twitter framework. Visit iOSDeveloperTips.com for more information." 
                                  forKey:@"status"] requestMethod:TWRequestMethodPOST];
 
        // Post the request
        [postRequest setAccount:acct];
 
        // Block handler to manage the response
        [postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) 
        {
          NSLog(@"Twitter response, HTTP response: %i", [urlResponse statusCode]);
        }];
      }
    }
  }];
}

On line 14, the request for access will show a dialog as follows:

If all is well, we create an array of the available accounts, and using the first entry, create a TWRequest to post a tweet. Check out the Twitter API for specifics on the parameters.

Once the tweet has been posted, there is block handler to print the response to the console. A successful tweet should print: Twitter response, HTTP response: 200.

The successful Twitter post looks will look like this:

One last note, don’t forget to add the Twitter and Accounts frameworks to your project before compiling.

20 comments

Nice tutorial!
You get The search results in a NSDictionary dont you?
But how can you parse it? Usually you would use objectForKey:.
Or if i request the home timeline, how can i get the first tweet?
Thanks in advance,
Lars :)

by Lars on Oct 24, 2011. #

Hi Lars,

You can access the dictionary as you would any other dictionary object. For example, here is how you can print out the query string:

NSLog(@”query: %@”, [dict objectForKey:@”query”]);

John

by John Muchow on Oct 24, 2011. #

Hi.. thanks for the post!! I’m having a problem when the debugger reaches line 14 it bails to line 44. is there a way to figure out why?
in the settings -> Twitter -> I have 2 valid accounts AND the application name shows up bellow with the “Allows these apps to use you account” set to ON…

This block stuff is really cool but a PAIN to debug… :(

by CocoaNewBee on Oct 28, 2011. #

The multi threading is really creating havoc on my little pea brain. using the debugger, the first pass fails on the [postRequest performRequestWithHandler]…. which makes NO sense to me… I would beg you to make a more comprehensive tutuorial on the processing of information. The vanilla processing you are showing is awesome !!! but for teh new cocoa heads like me, makes it very diffcult to understand the processing on of the data after the postRequest!!

Thank you very much for your time!!

by W on Nov 3, 2011. #

I am getting a 401 back. I am using the accounts code above but nothing is popping up and asking if I would like to access to twitter accounts.

by Kenneth Lewis on Oct 29, 2011. #

Kenneth, do you already have at least one twitter account configured in your device?

by Hans on Nov 1, 2011. #

Hi,
thank you for the sample it was really helpful.
I would like to know how to write a correct loop with blocks for each twitter account if the block code itself has a block call using a twitter API. I tried using dispatch_sync but still the code does not execute in the required order.
Thank you for any help.

here is my loop (am trying to know if ANY of the twitter accounts on the device “follows” a certain account which is passed as a parameter

__block id blockDelegate = _delegate;
__block BOOL accountFound = NO;

[store requestAccessToAccountsWithType:twitterType withCompletionHandler:^(BOOL granted, NSError *error)
{
if (granted)
{
// access granted
NSArray *twitterAccounts = [store accountsWithAccountType:twitterType];

if ([twitterAccounts count] > 0)
{
NSLog(@”******FOR LOOP START*******”);
for (ACAccount *account in twitterAccounts)
{
NSLog(@”processing Account….”);
NSURL *url = [NSURL URLWithString:@”http://api.twitter.com/1/friendships/exists.json”];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:account.username, @”screen_name_a”, user_as_method_parameter, @”screen_name_b”, nil];
TWRequest *request = [[TWRequest alloc] initWithURL:url parameters:params requestMethod:TWRequestMethodGET];

[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
{
dispatch_sync( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^{

NSString *response = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(@”Response was: %@”, response);
BOOL doesFollow = [response boolValue];
[response release];
// check for friendship
if( (doesFollow == YES) && (accountFound == NO) )
{
NSLog(@”at least one account found!!!”);
accountFound = YES;
}
// });
});
}];
}
NSLog(@”+++++++FOR LOOP END++++++++”);

if( accountFound )
{
if ((blockDelegate != nil) && [blockDelegate respondsToSelector:@selector(doesFollowUser)])
{
NSLog(@”1- Account found ==> going to: DOES follow”);
[blockDelegate doesFollowUser];
}
}
else
{
if ((blockDelegate != nil) && [blockDelegate respondsToSelector:@selector(doesNotFollowUser)])
{
NSLog(@”2- No accounts found ==> going to: does NOT follow”);
[blockDelegate doesNotFollowUser];
}
}
}
else
{
if ((blockDelegate != nil) && [blockDelegate respondsToSelector:@selector(doesNotFollowUser)])
{
NSLog(@”3- No accounts found ==> going to: does NOT follow”);
[blockDelegate doesNotFollowUser];
}
}
}
}];

by Mike on Nov 2, 2011. #

Thanks for the tutorial! It’s been a great help, although I could use one more tip. I’m trying to simply populate a UITableViewController with the list of accounts, but the multithreading is throwing me off. I try to insert rows within the callback, but something is causing the interface to hang.

Any chance you could do a part 3 that shows how to feed these accounts into a simple TableView?

by Mike on Nov 3, 2011. #

Great tutorial, have you tried to tweet an image via TWRequest, using something like http://api.twitter.com/1/statuses/update_with_media.json

not having much luck on my side but it seems like it could work?

thanks!

by stuart on Nov 7, 2011. #

Thanks for a great tutorial!

Do you know if it’s possible to send images using TWRequest as well? Using something like http://api.twitter.com/1/statuses/update_with_media.json ?

by stuart on Nov 7, 2011. #

Great tutorial!! (as usual)
I’ve tried to post an image with https://upload.twitter.com/1/statuses/update_with_media.json using addMultiPartData to the TwRequest and I got it!! but… only the image was uploaded and tweeted, not the status text…
¿Any idea?

by isolMAC on Nov 18, 2011. #

Replying to myself, it’s necessary to create TWRequest passing nil as parameter, and add both status and image as MultiPartData.

by isolMAC on Nov 23, 2011. #

I’m stacked same problem.
Thank you for your self comment, it is helpful for me!

by jaz on Dec 18, 2011. #

Thank you – this was perfect. I was up and running in less than 5 minutes.

by Ben Williams on Nov 23, 2011. #

Anybody tried this since Jan 6th, 2012?

I have had this implemented in my app for the past month. It was working great, BOTH methods. However, just yesterday it suddenly stopped working.

I created a new app from scratch and put in the code just to test out twitter only and I get the same response. Twitter sends me back a code 403!! The connection to twitter seems to consistently fail now.

I can send tweets from my twitter app on my iphone, but not from my developed apps.

Anybody else experiencing this?

Did twitter change the URL to send updates to?

Any other thoughts?

Thanks

by Moomio on Jan 6, 2012. #

This is a follow up reply to my last comment:

I went into my settings and deleted my twitter account entirely from my device and then logged in with a different twitter account.

The code above started working again both implementations.

So, I removed that twitter account from my device and logged in with the original twitter account I used for testing.

Same thing happened, couldn’t send a single tweet from my device, Twitter returned 403 code!

It appears that twitter has blocked my account from receiving tweets from my app.

Is this possible?

Did I send too many of the same tweets during testing of my app? It appears that over the past month of testing I sent about 230 tweets that were about the same!

Any insight into this?

So if you are having problems with the above codes not working, just try switching twitter accounts before you spend hours reprogramming!

by Moomio on Jan 6, 2012. #

Did you found the reason for this Twitter error 403? I just switched to the iOS-Twitter Framework and some of my users have the same problem.

by Oliver Fürniß on May 18, 2012. #

Hello,

Thank you so much for the tutorial. What I was wondering is, if we we the example in part 1, what will happen on a device that does not run iOS5. As less than 5. Will it fail silently or will the app crash?

by Chetan on Feb 11, 2012. #

You can check if the iOS version is 5.0 or greater, or you can use NSClassFromString to see if a specific class is available.

by John Muchow on Feb 15, 2012. #

Thanks for such a nice tutorial. Whenever I use it in a small demo project , everything works fine.
But whenever I use the same code in my original app project, and I try to print

[request performRequestWithHandler:

^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {

if(responseData)
{
NSDictionary *dict =

(NSDictionary *)[NSJSONSerialization

JSONObjectWithData:responseData options:0 error:nil];

// Log the result

NSLog(@”Response is %@”, dict);

The NSLog prints
Response is {
error = “Missing or invalid url parameter.”;
request = “/1/statuses/update_with_media.json?application_id=com.mybundle.identifier”;
}

I dont seem to understand about error = “Missing or invalid url parameter.”;

by Priyanshu on Oct 26, 2012. #