Chapter 14

Twitter Recipes

Since its incipience, the Twitter service has been rapidly expanding as a means of communication, advertising, and even organization. Its use has become widespread throughout modern stechnology and entertainment, allowing an unprecedented amount of masscommunication. With the release of iOS 5.0, the addition of a Twitter framework allows developers to work and program more efficiently with this incredible interface. This has opened up a whole category of app possibilities, from sending simple tweets to analyzing trending topics to suggesting articles to a user. By utilizing this framework in our applications, we are able to incorporate a variety of functionalities into our applications that allow us to communicate with the Twitter service, allowing users to contribute further to the ever-growing worldwide network of communication.

Recipe 14–1: Composing Simple Tweets

The core foundation of the Twitter service is the idea of sending “tweets”. These are composed of quick bits of information, limited to 140 characters, and sometimes accompanied by an image or outside link. In the Twitter framework, you are able to access a pre-configured class that can easily send out these messages.

When making use of the Twitter framework, any functionality you build that revolves around accessing a specific Twitter account for sending or retrieving information will not actually work on your iOS simulator, as you cannot connect it to a Twitter account. Therefore, you will opt to test all of your Twitter-based recipes on a physical device.

In order to test those functionalities that require an account on your device, you need to make sure that your device is configured with at least one Twitter account. You can create an account through the Twitter website, or through the Twitter iOS app. Once you have an account, you must provide your device with your login information. This configuration can be accessed from your Settings app on your device, and will resemble Figure 14-1.

Image

Figure 14-1. Configuring your Twitter account on your device

By having a Twitter account configured with your device, you will be able to fully test all the functionality that you will create.

Next, you start by creating a new project called “Tweeter” in Xcode. Select the SingleView Application template, and then click through to create your project. If you are running the newest version of Xcode, make sure also that the box marked “Automatic Reference Counting” is checked, as in Figure 14-2.

Image

Figure 14-2. Configuring project settings

For all the recipes in this chapter, you need to include the Twitter framework into your project.

After selecting your project, navigate to the Build Phases tab, and in the section marked “Link Binary With Libraries, click the + button. Find the item called Twitter.framework, and add it, as shown in Figure 14-3.

Image

Figure 14-3. Adding the Twitter framework

You will also later need the Accounts framework, so repeat this process, adding the item called Accounts.framework.

Add import statements to your view controller's header file so that the compiler allows you to use your added frameworks.

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

Next you will set up your initial user interface. Add a UIButton to your view, and connect it to your controller using theproperty name simpleTweetButton. Connect it to an IBAction method called -simpleTweetPressed:, as shown in Figure 14-4.

Image

Figure 14-4. Setting up a simple user interface

Let's set up a convenience method to check if your application can send a tweet at any given moment. If not, you will disable and dim your UIButton.

-(BOOL)checkCanTweet
{
if ([TWTweetComposeViewController canSendTweet])
    {
self.simpleTweetButton.enabled = YES;
self.simpleTweetButton.alpha = 1.0;
return YES;
    }
else
    {
self.simpleTweetButton.enabled = NO;
self.simpleTweetButton.alpha = 0.6;
return NO;
    }
}

This method makes use of the +canSendTweet class method of TWTweetComposeViewController. You will be using this class to allow creation of simple tweets shortly.

In order to make sure your view controller correctly rechecks your functionality, you will place a call to this function in both -viewDidLoad and -viewWillAppear:animated:.

[self checkCanTweet];

It is also possible that the device your application is on could have a Twitter account configured at any moment, so you can also register for notifications to be sent upon the changing of account information. Add the following line to your -viewDidLoad method.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkCanTweet)
name:ACAccountStoreDidChangeNotification object:nil];

Now you will implement your -simpleTweetPressed: method to configure and send a simple tweet.

-(void)simpleTweetPressed:(id)sender
{
if ([self checkCanTweet])
    {
TWTweetComposeViewController *tweet = [[TWTweetComposeViewController alloc] init];
        [tweet setInitialText:@"Posting a simple Tweet from my app!"];
        [tweet setCompletionHandler:^(TWTweetComposeViewControllerResult result)
        {
if (result == TWTweetComposeViewControllerResultDone)
           {
NSLog(@"Tweet Successfully Sent");
           }
else
           {
NSLog(@"Tweet Cancelled");
           }
           [self dismissModalViewControllerAnimated:YES];
        }];
        [self presentModalViewController:tweet animated:YES];
    }
}

As shown here, you can create an initial text for your tweet, as well as set a completion handler to be called after your TWTweetComposeViewController is either cancelled or completed.

You are able to add, in addition to your initial text, images and links to be attached to your tweets by using the -addImage:and addURL: methods. Figure 14-5 demonstrates your current configuration, along with an added link.

Image

Figure 14-5. Your view presenting a TWTweetComposeViewController

The three methods that are used to add content to your tweet, -setInitialText:,-addImage:, and -addURL:, all return BOOL values indicating whether the content was successfully added. These values will be NO if either the content does not fit in the tweet, or if the controller has already been presented.

In order to ensure that the users always have the last say in what is sent in a tweet from this controller, the developer is not allowed to set or add any content once it has been presented.

If at any point you wish to remove content from a controller before it is presented, you can make use of the -removeAllImages and -removeAllURLsmethods.

Recipe 14–2: Creating Simple TWRequests

Aside from the TWTweetComposeViewController, the only other class in the Twitter framework is the TWRequest class. This class is incredibly general, and makes use of Twitter's specific API in order to send and request information. Through its use, you can mimic the basic function of the TWTweetComposeViewController, as well as perform nearly any command you want to incorporateinyour application with the Twitter service.

It is important to remember that the Twitter API changes differently from the iOS API, so any Twitter-specific code in this section may have to be updated to the newest version.

Sending Tweets via TWRequest

You will add another UIButton to your view in order to implement a more complex functionality, with label “Post Tweet”, property name postTweetButton, and action handler -postTweetPressed:. Place it underneath the first button, as in Figure 14-6.

Image

Figure 14-6. Adding another button to your interface

You will implement your -postTweetPressed:.This method will contain the following steps:

  1. Access the device's account store, which has access to any accounts registered with the device.
  2. Create an instance of ACAccountType to specify in your request for all stored Twitter accounts.
  3. Request access to all accounts of the specified type. The -requestAccessToAccountsWithType:withCompletionHandler: method will prompt the user as to whether the application has permission to access the accounts. If permission has already been acquired by the application previously, the user will not be prompted.
  4. Access an array of ACAccount objects from the account store referencing all registered Twitter accounts on the device.
  5. Access a specific account to post with.
  6. Initialize an instance of TWRequest. This class is given instructions as to the specific request being formed.
  7. Set your chosen ACAccount to the TWRequest.
  8. Perform your request and utilize your completion handler to analyze the results.
-(IBAction)postTweetPressed:(id)sender
{
ACAccountStore *accountStore = [[ACAccountStore alloc] init];

ACAccountType *accountType = [accountStore
accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];

    [accountStore requestAccessToAccountsWithType:accountType
withCompletionHandler:^(BOOL granted, NSError *error)
    {
if (granted)
        {
NSArray *accountsArray = [accountStore accountsWithAccountType:accountType];

if ([accountsArray count] >0)
            {
ACAccount *twitterAccount = [accountsArray objectAtIndex:0];

TWRequest *postRequest = [[TWRequest alloc] initWithURL:[NSURLURL
WithString:@"http://api.twitter.com/1/statuses/update.json"] parameters:[NSDictionary
dictionaryWithObject:@"Posted with a TWRequest!"forKey:@"status"]
requestMethod:TWRequestMethodPOST];

                [postRequest setAccount:twitterAccount];

                [postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
                {
if ([urlResponse statusCode] == 200)
                    {
NSLog(@"Tweet Posted");
                    }
else
                    {
NSLog(@"Error Posting Tweet");
                    }
                }];
            }
        }
    }];
}

The key component to this method is, of course, the initialization of the TWRequest. Here you have made careful use of the Twitter API in order to configure your tweet post with three properties:

  • URL: The string used to make your URL, http://api.twitter.com/1/statuses/update.json, is specially used to inform the Twitter service that you are initiating a status update.
  • Parameters:Youspecify an instance of NSDictionary with a single key “status”, to which you assign an NSString with the actual text of your tweet.
  • Request method: This parameter specifies the type of request that you are making. Since you are posting a tweet, you use the TWRequestMethodPOST value.

The interesting thing about posting tweets using this method is that your user does not actually get a preview of the tweet before it is sent. In fact, the only indication the user receives in this case is the request for access to the Twitter account. It is important to keep the user's best interests in mind when sending such posts, so as not to post unwanted updates.

NOTE: As shown in the preceding method, you can evaluate the results of a TWRequest by checking the statusCode of the urlResponse. If this value is 200, the request was successfully completed. Otherwise, there was some sort of error. Refer to the Twitter API at https://dev.twitter.com/docs/error-codes-responses for specific details on all the various error codes.

Upon testing this app, you will be able to, after requiring permission, send tweets from your device's registered account without using the TWTweetComposeViewController.

Recipe 14–3: Retrieving Tweets

Now that you have covered two different methods with which you can post updates to Twitter, you can apply the concepts used in the previous recipe, revolving around the TWRequest class, in order to build an application that can acquire and display tweets.

You will build an application to display multiple different groups of tweets, so you will use a UITabBarController to keep your content groups separate. Open up a new project, and start off by selecting the Tabbed Application template, as in Figure 14-7.

Image

Figure 14-7. Creating a tabbed application

On the next screen, enter your project name. Since you will be displaying groups of tweets from a variety of sources, name your project “PulseOfTheWorld” with the class prefix of “Main”. Make sure your application is configured for the iPhone device family and that Automatic Reference Counting is enabled, so that your project settings are the same as those in the previous recipe. Click through to create your project.

The template application will resemble that simulated here. Luckily for you, this layout is exactly how you will start your application off, as demonstrated in Figure 14-8, so you can display the raw data acquired from Twitter.

Image

Figure 14-8. Your generic tabbed application

NOTE: Since you have created a template application with two view controllers that you will format similarly, the following configuration will include changes made to both controllers simultaneously. Make sure to follow carefully and make all necessary changes to each view controller.

You will first need to configure your user interface to be programmatically editable, so first go into the XIB file for each of your two pre-made view controllers and connect both the UILabel and UITextView to properties in your header files. You will use the respective property names publicLabel and publicTextView in the first view controller, and homeLabelandhomeTextView in the second view controller.

You can change the initial text of your view elements to fit your application a bit more. Add the following lines to the -viewDidLoad of your first view controller.

self.publicLabel.text = @"Public Timeline";
self.publicTextView.text = @"Public Timeline data not retrieved yet.";

Add the equivalent lines of code to your second view controller.

self.homeLabel.text = @"Home Timeline";
self.homeTextView.text = @"Home Timeline data not retrieved yet.";

You'll also add a UIButton to each of your view controllers to actually trigger your application to retrieve data. Name each button either “Get Public Timeline” or “Get Home Timeline” as appropriate, and connect them to IBAction methods with handlers -publicPressed: and -homePressed as well as to properties publicButton and homeButton.

Before you proceed, you need to make sure to include your Twitter and Accounts frameworks in your project. Follow the same procedure discussed in the previous recipes to add them both, and make sure the correct import statements are added to both view controllers.

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

Back in the XIB file, you need to slightly customize your UITextViews to work correctly with your application. Make sure that for each UITextView the box marked “User Interaction Enabled” is checked, as in Figure 14-9, in order to allow the views to scroll.

Image

Figure 14-9. Enabling user interaction for your UITextViews

Now make sure to set each UITextView's text alignment to the left, so that your raw data is slightly more readable. I have also increased the size of these views so as to be able to display more information at once.

At this point, your views should resemble Figure 14-10, with appropriately different text for each controller.

Image

Figure 14-10. Your configured user interface

Also following is the header code for the first view controller that your program should now resemble. The second view controller's header file should be identical aside from the different property and action names.

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

@interface MainFirstViewController : UIViewController
@property (strong, nonatomic) IBOutlet UILabel *publicLabel;
@property (strong, nonatomic) IBOutlet UITextView *publicTextView;
@property (strong, nonatomic) IBOutlet UIButton *publicButton;
-(IBAction)publicPressed:(id)sender;
@end

Following the lines of your previous recipes, you will create a method to check for Twitter availability, and adjust your view accordingly. You need this method only in your second view controller, so it will look like so:

-(BOOL)checkCanTweet
{
if ([TWTweetComposeViewController canSendTweet])
    {
self.homeButton.enabled = YES;
self.homeButton.alpha = 1.0;
return YES;
    }
else
    {
self.homeButton.enabled = NO;
self.homeButton.alpha = 0.6;
return NO;
    }
}

Just as before, make sure to place a call to the -checkCanTweet method in both your -viewDidLoad and -viewWillAppear:animated: methods.

Now you can go ahead and work on implementing your methods to request your tweets. As you may have guessed, you will start off by retrieving data from the Public Timeline in your first view controller, and data from the Home Timeline in the second. In this case, the Home Timeline refers to the posts made by the current user, as well as any users they are following, as opposed to the Public Timeline, which simply shows public tweets. Because the Home Timeline is user-specific, you will need to make use of the Accounts framework again to access your device's registered Twitter account(s).

You will start off by implementing your -publicPressed: method in your first view controller to retrieve public tweets with the following code.

- (IBAction)publicPressed:(id)sender
{
TWRequest *postRequest = [[TWRequest alloc] initWithURL:[NSURL
URLWithString:@"http://api.twitter.com/1/statuses/public_timeline.json"]
parameters:nilrequestMethod:TWRequestMethodGET];

    [postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse
*urlResponse, NSError * error)
    {
        NSString *output;
if ([urlResponse statusCode] == 200)
        {
            NSError *jsonParsingError;
NSArray *publicTimeline = [NSJSONSerialization JSONObjectWithData:responseData
options:0error:&jsonParsingError];

            output = [NSString stringWithFormat:@"Public timeline: %@",
publicTimeline];
        }
else
        {
            output = [NSString stringWithFormat:@"HTTP response status: %i ",
[urlResponse statusCode]];
        }
        [self.publicTextView performSelectorOnMainThread:@selector(setText:)
withObject:output waitUntilDone:NO];
    }];
}

This method can be broken down into several points:

1. Because the Public Timeline is available openly, you did not need to access a specific Twitter account to retrieve the data, so you went straight to configuring your TWRequest.

2. Similarly to your previous recipe, when creating your TWRequest, you used a URL specific to Twitter to access your intended service. The URL used here, http://api.twitter.com/1/statuses/public_timeline.json, is written to give you the data from the Public Timeline specifically in the JSON format (as specified by the “json” suffix).

3. Also when configuring the TWRequest, you must specify the “request method” value TWRequestMethodGET, instead of the previous TWRequestMethodPOST. The easiest way to view this is that you use the “GET” value to retrieve information, and the “POST” value to send.

4. After confirming the successful response, you have to convert your received data into a useful iOS format. Since you specified your data to use the JSON format, you can make use of the NSJSONSerialization class method +JSONObjectWithData:options:error:. This method converts your received JSON data into either an NSDictionary or NSArray, depending on the format. In this case, you receive an NSArray, whose contents you then place into the NSStringoutput.

5. Any changing of the user interface must be performed in the main thread, so you make use of the -performSelectorMainThread:withObject:waitUntilDone: method to update your user interface with the given output.

It is also important to know that you are able to further customize your TWRequest by specifying parameters, which determine what kind of results you receive. Every kind of request has its own set of available parameters, so refer to the Twitter API to find more information on specific options, which are set in an NSDictionary.

Now, in your second view controller, you can implement a similar solution to retrieve the user's Home Timeline data.

- (IBAction)homePressed:(id)sender
{
if ([self checkCanTweet])
    {
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore
accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];

        [accountStore requestAccessToAccountsWithType:accountType
withCompletionHandler:^(BOOL granted, NSError *error)
        {
if (granted)
            {
NSArray *accountsArray = [accountStore accountsWithAccountType:accountType];
if ([accountsArray count] >0)
                {
ACAccount *twitterAccount = [accountsArray objectAtIndex:0];

TWRequest *postRequest = [[TWRequest alloc] initWithURL:[NSURL
URLWithString:@"http://api.twitter.com/1/statuses/home_timeline.json"] parameters:nil
requestMethod:TWRequestMethodGET];

                    [postRequest setAccount:twitterAccount];

                    [postRequest performRequestWithHandler:^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error)
                     {
                         NSString *output;

if ([urlResponse statusCode] == 200)
                         {
                             NSError *jsonParsingError;
NSArray *homeTimeline = [NSJSONSerialization JSONObjectWithData:responseData options:0
error:&jsonParsingError];

                             output = [NSString stringWithFormat:@"Home timeline: %@",
homeTimeline];
                         }
else
                         {
                             output = [NSString stringWithFormat:@"HTTP response status:
%i ", [urlResponse statusCode]];
                         }
                         [self.homeTextView
performSelectorOnMainThread:@selector(setText:) withObject:output waitUntilDone:NO];
                     }];
                }
            }
else
            {
self.homeTextView.text = @"Error, Twitter account access not granted.";
            }
        }];
    }
}

This method follows the same track as the previous one, with the following differences:

6. You included a quick check to -checkCanTweet in order to ensure that your device is properly configured with a Twitter account.

7. Since you are accessing a specific user's timeline, you include code to access the device's registered Twitter accounts, pick the first, and include it in the TWRequest.

8. A slightly different URL, this time http://api.twitter.com/1/statuses/home_timeline.json, is used to request a Home Timeline, as opposed to the Public Timeline.

If you run your application on a device at this point, you should be able to retrieve a good 20 tweets worth of data to your phone and view their raw data. Figure 14-11 shows the beginning of the Public Timeline data at the moment of this writing.

Image

Figure 14-11. Sample public timeline data

As you scroll through this massive amount of data, you can get a general sense of the format of the tweet data that you've received. You have an NSArray of a group of tweets, with each one stored as an NSDictionary. The values you see in Figure 14-11 represent the keys (on the left) and their related objects (on the right). These objects are formatted as instances ofNSString, NSNumber, NSNull, or even more complex instances of NSArray or NSDictionary. For example, to access the name of a tweet's poster, you must access a nested dictionary within your array. You first find the tweet in your array, then access the NSDictionary containing user information through the “user” key, and then retrieve the name with the “name” key. Using this information, you can begin to build a more complete application to display your tweets.

First, you will create a simple data model to encapsulate your post data into custom objects. Create a new file using the “Objective-C class” template. Name your class “Post”, and make sure the subclass field is set to “NSObject”, as in Figure 14-12.

Image

Figure 14-12. Configuring your NSObject subclass called “Post”

Next you can define properties for your Post object to have. You will specify properties for some of the most commonly used properties of a tweet, such as the text, user's name, and retweet count. You will also have properties to keep track of your actual NSDictionaries of data in order to simplify accessing any additional values later.

In order to create your Post objects, you will use a designated initializer that will accept the NSDictionary objects that you saw earlier as a parameter. Define these properties and method so that your Post.h file reads like so:

#import <Foundation/Foundation.h>

@interface Post : NSObject

@property (nonatomic, strong) NSDictionary *postData;
@property (nonatomic, strong) NSDictionary *user;
@property (nonatomic, strong) NSString *text;
@property (nonatomic, strong) NSString *screenName;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *userDescription;
@property (nonatomic, strong) id retweetCount; //Could be NSString (100+) or NSNumber
@property (nonatomic, strong) UIImage *userImage;
-(Post *)initWithDictionary:(NSDictionary *)dictionary;

@end

As commented here, you have made the property retweetCount of type “id”, due to the possibility of it having two different types of values. If you looked through your earlier raw data, you might have noticed some posts with “retweet_count” values of numbers (i.e., 0, 10, etc.), and others with the string “100+”. By making your property a general type, you can later check to see which value you ended up with for any given post.

You can synthesize all these properties in a single line in the Post.m file.

@synthesize name, text, user, postData, screenName, retweetCount, userDescription,
userImage;

Your initializer is fairly easy to set up, and just involves you querying your dictionaries with the various keys you will use.

-(Post *)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self)
    {
self.postData = dictionary;
self.user = [dictionary objectForKey:@"user"];
self.text = [dictionary objectForKey:@"text"];
self.retweetCount = [dictionary objectForKey:@"retweet_count"];
self.name = [self.user objectForKey:@"name"];
self.screenName = [self.user objectForKey:@"screen_name"];
self.userDescription = [self.user objectForKey:@"description"];
NSString *imageURLString = [self.user objectForKey:@"profile_image_url"];
NSURL *imageURL = [NSURL URLWithString:imageURLString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
self.userImage = [UIImage imageWithData:imageData];
    }
return self;
}

In this method, you may notice that you have not chosen to dispatch an alternate thread to retrieve your image data for each post. This will probably block your main thread for an extra second or two, but since you have no need to display posts before their image content is retrieved, you can work with the pause.

Now that your file is configured, you'll need to use it in your two view controllers, so add an import statement to each header file.

#import "Post.h"

Now, in each of your view controllers, you will add an NSArray property to store your retrieved posts.

@property (strong, nonatomic) NSMutableArray *retrievedTweets;

After synthesizing this property, create a custom getter to ensure that the array is correctly initialized.

-(NSMutableArray *)retrievedTweets
{
if (retrievedTweets == nil)
    {
retrievedTweets = [NSMutableArray arrayWithCapacity:20];
    }
return retrievedTweets;
}

Now, you'll do some rearranging of your user interface. Instead of simply giving a UITextView of your output, you'll organize your information into a UITableView. Remove your previously created UIButton,UILabel, and UITextView from each view controller's XIB file and replace it with a UITableView, as shown in Figure 14-13. Connect this to a property tableViewPosts in each respective view controller. I've also removed the previously used properties, though this is optional, and requires removing several lines of problematic code from your implementation file.

Image

Figure 14-13. Adding a UITableView to your user interface

You'll need to go back and modify your -viewDidLoad method to set your table view's delegate and data source. Since you already have your method to retrieve data built, you can also include this.

- (void)viewDidLoad
{
[super viewDidLoad];
    [self publicPressed:nil];
self.tableViewPosts.delegate = self;
self.tableViewPosts.dataSource = self;
}

Your second view controller's -viewDidLoad method will look the same, but with a call to -homePressed:.

Make sure also to conform your view controllers to the UITableViewDelegate and UITableViewDataSource protocols.

Now your updated version of your -publicPressed: method in your first view controller will look like so:

- (IBAction)publicPressed:(id)sender
{
    [self.retrievedTweets removeAllObjects];

TWRequest *postRequest = [[TWRequest alloc] initWithURL:[NSURL
URLWithString:@"http://api.twitter.com/1/statuses/public_timeline.json"] parameters:nil
requestMethod:TWRequestMethodGET];

    [postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse
*urlResponse, NSError *error)
    {
if ([urlResponse statusCode] == 200)
        {
            NSError *jsonParsingError;
NSArray *publicTimeline = [NSJSONSerialization JSONObjectWithData:responseData options:0
error:&jsonParsingError];
for (NSDictionary *dict in publicTimeline)
            {
Post *current = [[Post alloc] initWithDictionary:dict];
                [self.retrievedTweets addObject:current];
            }
        }
else
        {
NSLog(@"%@", [NSString stringWithFormat:@"HTTP response status: %i ",
[urlResponse statusCode]]);
        }
        [self.tableViewPosts reloadData];
    }];
}

With similar changes, the -homePressed: method will appear as shown here:

- (IBAction)homePressed:(id)sender
{
if ([self checkCanTweet])
    {
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore
accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];

        [accountStore requestAccessToAccountsWithType:accountType
withCompletionHandler:^(BOOL granted, NSError *error)
        {
if (granted)
            {
NSArray *accountsArray = [accountStore accountsWithAccountType:accountType];
if ([accountsArray count] >0)
                {
                    [self.retrievedTweets removeAllObjects];

ACAccount *twitterAccount = [accountsArray objectAtIndex:0];

TWRequest *postRequest = [[TWRequest alloc] initWithURL:[NSURL
URLWithString:@"http://api.twitter.com/1/statuses/home_timeline.json"] parameters:nil
requestMethod:TWRequestMethodGET];
                    [postRequest setAccount:twitterAccount];

                    [postRequest performRequestWithHandler:^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error)
                     {                         
if ([urlResponse statusCode] == 200)
                         {
                             NSError *jsonParsingError;
NSArray *homeTimeline = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&jsonParsingError];
for (NSDictionary *dict in homeTimeline)
                             {
Post *current = [[Post alloc] initWithDictionary:dict];
                                 [self.retrievedTweets addObject:current];
                             }
                         }
else
                         {
NSLog(@"%@", [NSString stringWithFormat:@"HTTP response status: %i ", [urlResponse statusCode]]);
                         }
                 [self.tableViewPosts reloadData];
                     }];
                }
            }
else
            {
NSLog(@"Error, Twitter account access not granted.");
            }
        }];
    }
}

In the previous two methods, you needed to include a call to the -reloadData method in order to make sure that your tables are correctly updated once all your data has been fully retrieved. Otherwise, the tables will simply be displayed empty before you have your information.

Now, you just need to implement your delegate and datasource methods to configure your UITableViews.

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.retrievedTweets count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
 {
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
    {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

Post *current = [self.retrievedTweets objectAtIndex:indexPath.row];
    cell.textLabel.text = current.text;
    cell.detailTextLabel.text = current.screenName;
    cell.imageView.image = current.userImage;

return cell;
}

These two methods can be copied into both view controllers with no changes.

Upon running your application now, you will be able to view two different tables of Twitter timelines, complete with text, screenname, and user image! Each table might take a few seconds to load depending on your device's connection speed.

If, when testing your application, you wish to have your tables update, you can remove the -publicPressed: or -homePressed: calls from your -viewDidLoadmethods, and move them into your -viewWillAppear:animated:methods.

Recipe 14–4: Filtering Tweets

In addition to the retrieving of tweets you have already done, you are also able to specifically filter the posts that you receive based on a variety of criteria.

Building on your previous recipe, add a new view controller to your project, calling it “MainSearchViewController”.

You will build this controller's user interface to be similar to that of your first view controller, with the exception of a UISearchBar that you will use to specify your search parameters, as in Figure 14-14. Make sure that “Correction” has been turned off for this search bar so that your user is not bothered by autocorrect.

Image

Figure 14-14. Adding a UISearchBar to your MainSearchViewController.xib file

Set the outlet name for the UISearchBar to be searchBarPosts.

After making sure to correctly import your frameworks and Post.h file into this new view controller and setting the proper protocols to be conformed to, you can copy your UITableView delegate/datasource methods from your first view controller. Make sure also to set up your NSMutableArray property retrievedTweets, and copy its customized getter.

In order to configure your UISearchBar, you will also need to conform your view controller to the UISearchBarDelegate protocol.

Set up your -viewDidLoad like so:

- (void)viewDidLoad
{
    [superview DidLoad];

self.tableViewPosts.delegate = self;
self.tableViewPosts.dataSource = self;
self.searchBarPosts.delegate = self;
}

In your application delegate's implementation file, import the new controller's header file.

#import "MainSearchViewController.h"

Now you need to update your -application:didFinishLaunchingWithOptions:method to include your newest view controller.

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
UIViewController *viewController1 = [[MainFirstViewController alloc]
initWithNibName:@"MainFirstViewController" bundle:nil];
UIViewController *viewController2 = [[MainSecondViewController alloc]
initWithNibName:@"MainSecondViewController" bundle:nil];
UIViewController *viewController3 = [[MainSearchViewController alloc]
initWithNibName:@"MainSearchViewController" bundle:nil];
self.tabBarController = [[UITabBarControlleralloc] init];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1,
viewController2, viewController3, nil];
self.window.rootViewController = self.tabBarController;
    [self.window makeKeyAndVisible];
return YES;
}

You can also add a slightly altered designated initializer back in your MainSearchViewController.m file to correctly set your new view controller:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(@"Filtered Posts", @"First");
self.tabBarItem.image = [UIImage imageNamed:@"first"];
    }
return self;
}

To fully configure your UISearchBar, you can create the delegate method -shouldChangeTextInRange:replacementText:like so, assuming that your data retrieval method in this controller will be called -searchPressed:.

-(BOOL)searchBar:(UISearchBar *)searchBar shouldChangeTextInRange:(NSRange)range
replacementText:(NSString *)text
{
if ([text isEqualToString:@" "])
    {
        [searchBar resignFirstResponder];
        [self searchPressed:searchBar.text];
return NO;
    }
return YES;
}

Now, you must consider the actual formatting of your request to Twitter. According to the Twitter API, the best way to get a URL for a specific search is to enter the search terms into the Twitter website search, and then modify the resulting URL that you are redirected to. To give a simple example, a search for the word “test” will redirect you to the URL http://twitter.com/#!/search/test. You then modify all but the suffix search term of this to http://search.twitter.com/search.json?q=test. In order to process simple search queries, you must correctly handle any spaces, hashmarks, “@” symbols, and quotation marks. In your code, you will have to manipulate your search terms to implement this behavior.

First, you can do most of your character replacements in a single line.

searchText = [searchText stringByAddingPercentEscapesUsingEncoding:
NSUTF8StringEncoding];

This method will properly convert all the aforementioned symbols with the exception of the “@” symbol, which you can manually replace like so:

NSMutableString *mutableText = [[NSMutableString alloc] initWithString:searchText];
[mutableText replaceOccurrencesOfString:@"@" withString:[NSString
stringWithFormat:@"%%40"] options:NSLiteralSearch range:NSMakeRange(0, [mutableText
length])];

Now, you can fully assemble your URL by appending this result on top of your general search URL.

NSString *searchString = @"http://search.twitter.com/search.json?q=";
searchString = [searchString stringByAppendingString:mutableText];
NSURL *searchURL = [NSURL URLWithString:searchString];

With this functionality added into a method similar to your -publicPressed:, you end up with the overall method written as follows:

- (IBAction)searchPressed:(NSString *)searchText
{
    [self.retrievedTweets removeAllObjects];

    searchText = [searchText stringByAddingPercentEscapesUsingEncoding:
NSUTF8StringEncoding];

NSMutableString *mutableText = [[NSMutableString alloc] initWithString:searchText];
    [mutableText replaceOccurrencesOfString:@"@"
withString:[NSStringstringWithFormat:@"%%40"] options:NSLiteralSearch
range:NSMakeRange(0, [mutableText length])];

//    NSLog(@"Searching: %@", mutableText);

NSString *searchString = @"http://search.twitter.com/search.json?q=";
    searchString = [searchString stringByAppendingString:mutableText];
NSURL *searchURL = [NSURLURLWithString:searchString];

TWRequest *postRequest = [[TWRequest alloc] initWithURL:searchURL parameters:nil
requestMethod:TWRequestMethodGET];

    [postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse
*urlResponse, NSError *error)
     {
if ([urlResponse statusCode] == 200)
         {
             NSError *jsonParsingError;
NSDictionary *searchTimeline = [NSJSONSerialization JSONObjectWithData:responseData
options:0 error:&jsonParsingError];
NSArray *searchResults = [searchTimeline objectForKey:@"results"];
for (NSDictionary *dict in searchResults)
             {
Post *current = [[Post alloc] initWithSearchDictionary:dict];
                 [self.retrievedTweets addObject:current];
             }
//             NSLog(@"Data Retrieved: HTTP response code %i", [urlResponse
statusCode]);
         }
else
         {
NSLog(@"%@", [NSString stringWithFormat:@"HTTP response status: %i ", [urlResponse
statusCode]]);
         }
         [self.tableViewPosts reloadData];
         [self.tableViewPosts reloadInputViews];
     }];
}

Be careful to notice that in this case, the initial results you get after converting your JSON formatted data to an Objective-C object are not in an NSArray. This time, they are in an NSDictionary in order to provide extra information on the query. You can then access your usual NSArray by accessing the “results” key, as shown here.

When you retrieve your posts from asearch in this manner, your data is formatted with different keys than it was before. To make up for this, you will create a second designated initializer,-initWithSearchDictionary:, for your Post object, as used in the preceding method. This format does not give you quite as much information about the user in the default setting, so you will access only what you can. Make sure also to add this method's handler to your Post.h file.

-(Post *)initWithSearchDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self)
    {
self.postData = dictionary;
self.screenName = [dictionary objectForKey:@"from_user"];
self.text = [dictionary objectForKey:@"text"];
NSString *imageURLString = [dictionary objectForKey:@"profile_image_url"];
NSURL *imageURL = [NSURL URLWithString:imageURLString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
self.userImage = [UIImage imageWithData:imageData];
    }
return self;
}

At this point, your application should now be able to search for specific texts, hashtags, and users included in posts! You have included basic functionality to encompass the most commonly used search terms, but the Twitter API allows for far more complex queries to be made. For a full list of the variety of ways to format a search, refer to the Twitter API at https://dev.twitter.com/docs/using-search.

When you run your application now, you should be able to search for different hashtags, phrases, and usernames. Try searching for #miami and see what results you get!

NOTE: If at any point you are unsure of the format of your JSON data retrieved from Twitter, you can simply take the URL used to make your request and enter it in your web browser. You will receive a text view of the exact same data you would have gotten. It can be a little difficult to read due to a lack of space formatting, but you can look for the necessary layers of content needed to build your application.

Summary

Throughout this chapter, we have gone over a great deal of sample code making use of the Twitter framework, with subjects from pre-defined user interfaces to send tweets, to custom-formatted requests to acquire specifically filtered data directly from Twitter. Our basic implementations can easily serve as the groundwork of much more complex applications. Integration with the Twitter service is not simply limited to posting and displaying tweets, and can easily enhance the functionality and power of nearly any application. Considering the popularity and consistent growth of Twitter itself, it is certain that iOS's integration can only become more profound, allowing developers simpler access to one of the modern world's most powerful web services.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset