Authorizing Twitter in iOS

Introduction

In my previous article we created a simple app that interacts with the Twitter API and pulls down a single tweet from the public timeline. In a typical application, we want to pull tweets from a user’s timeline.

iOS5 provides an API for securing authorisation details centrally and providing access to them to any application that wants it. This means that users only have one place to go if they wish to de-authorise or modify account details. Neat.

Documentation

The Accounts Framework has notably sparse documentation for an iOS Framework. It is, however, relatively straightforward to use. The API is documented, but there’s no developer guide to using it at time of writing.

We’re going to gloss over how to add a new account to the authentication store for now, but you can find details on how to accomplish this in the ACAccountStore Class Reference

Building on Earlier Works

Adding the Accounts Framework

Before we can use the code in the Accounts Framework, we need to add it to our application. Scroll down to “Linked Frameworks and Libraries” in your Project’s Summary section.

add the accounts framework

Click “Add” and you’re done.

Modifying Headers

We’re going to add a new property to our viewController, ‘store’. This is a handle to the ACAccountStore, which we’ll need at several points in our application’s lifecycle.

Modify TWAPIExampleViewController.h to look like the following:

    #import <UIKit/UIKit.h>
    #import <Accounts/Accounts.h>
    #import <Twitter/Twitter.h>

    @interface TWAPIExampleViewController : UIViewController
        @property (nonatomic, retain) ACAccountStore *store;
        @property (nonatomic, retain) UILabel *tweetLabel;

    -(void)getTweet;

    @end

We’ve added an #import statement for the Accounts framework and added the store @property.

As usual, when we add a @property we should @synthesize its accessor and mutator methods – in TWAPIExampleViewController.m, modify the line that reads:

    @synthesize tweetLabel;

To be:

    @synthesize store, tweetLabel;

And done.

Getting Access to the Account Store

When the app loads, we need to get access to the account store. The Accounts Framework will ask the user to authorise this via a pop-up. If the user accepts, we’ll get access to the accounts stored in the Account Store (which, on a blank iOS Simulator instance, will be … none!)

Remember, we’re working with the code from the last article I wrote, which you can get access to via BuildMobile’s GitHub account.

In TWAPIExampleViewController.m, we’ll modify viewDidLoad to look like the following:

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.

        self.store = [[ACAccountStore alloc] init];
        ACAccountType *twitterAccountType = [self.store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];

        [self.store requestAccessToAccountsWithType:twitterAccountType withCompletionHandler:^(BOOL granted, NSError *error) {
            if(!granted) {
                abort();
                // We didn't get access, so we'll show an error and exit.
                // TODO: Show an error screen to the user and re-request permission!
            }
            return;
        }];

        self.tweetLabel = (UILabel*)[self.view viewWithTag:1];


        [self getTweet];
    }

See how we used abort() up there? Having calls to abort() in your code will probably mean your application won’t make it onto the App Store. You’ll need to replace that call with a nice error screen for the user, letting them know what went wrong and giving them a way to fix it.

That goes a bit beyond what I want to demonstrate here though, so like any good writer I’ll be lazy and skip over it.

Getting the Tweet

Now we need to modify our getTweet method to take advantage of the authorisation access we (should) have.

The Twitter API URL for getting access to a user’s timeline is ‘https://api.twitter.com/1/statuses/home_timeline.json’. So we’ll modify the URL we’re accessing accordingly.

    -(void)getTweet
    {

        NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/1/statuses/home_timeline.json"];   
        NSDictionary *parameters = [NSDictionary dictionaryWithObjectsAndKeys:@"1", @"count", nil];

        TWRequest *tweetRequest = [[TWRequest alloc] initWithURL:url parameters:parameters requestMethod:TWRequestMethodGET];    

        ACAccountType *twitterAccountType = [self.store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
        NSArray *accounts = [self.store accountsWithAccountType:twitterAccountType];

        if([accounts count] == 0) {
            // We need to get the user to add an account!
            // This is pretty cheap, but we'll zap them out to the Settings app, where they can add an account.
            // When they return, we'll re-run viewDidLoad to get access to the account.
            // TODO: Show a nice UI to your users to add/select which account they wish to use.
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"prefs:root=TWITTER"]];
            [self viewDidLoad];
            return;
        }

        ACAccount *account = [accounts objectAtIndex:0];
        //TODO: We should let the user choose which account they're using

        tweetRequest.account = account;

        [tweetRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
            // Request completed and we have data
            // Output it!

            NSError *jsonError = nil;
            id timelineData = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:&jsonError];

            if(timelineData == NULL) {
                NSString *myString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
                NSLog(@"Conversion to object failed.");
                NSLog(@"%d", [urlResponse statusCode]);
                NSLog(@"%@", myString);
                NSLog(@"%@", [jsonError localizedDescription]);
                abort();
                // TODO: Show a graceful error message here
            }

            NSDictionary *timelineDict = (NSDictionary*) timelineData;
            NSLog(@"%@", timelineDict);
            self.tweetLabel.text = [[(NSArray*)timelineDict objectAtIndex:0] objectForKey:@"text"];
        }];
    }

Still a busy function, but not it’s even busier! As last time, in practice you’d really want to break this functionality up a bit and not do cheesy things like re-calling viewDidLoad. I’m trying to condense my code into as few functions as possible for the sake of illustration.

So, from the top, we’re:

  1. Defining a new URL to access via the Twitter API (line 88 of that file)
  2. Getting the list of Twitter accounts in the store (lines 93 & 94)
  3. Handing the user off to the Settings App if there are no accounts configured (line 96). (We also call viewDidLoad again, which will re-start our authorisation process when the user re-enters the application. This is bad form.)
  4. We’re getting the first account on the stack (line 106)
  5. We’re setting the account on the tweet request (line 109).

And then, as before, we’re displaying the tweet we get back in a label.

Conclusion

This is a very basic example of how to use the Accounts Framework with the Twitter Framework to get access to a user’s twitter account. In practice, you’d want to use an OAuth library to allow the user to create Accounts (in the AccountStore) from within your application, and choose which account to use.

The code for this example is available on a branch on GitHub.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.