iOS

Build it yourself iOS Twitter client Part 3

In the final part of our Twitter app, we move on to the mentions feature, which is mostly the same code, only with a different Twitter API resource.

As mentioned in Part II, for the final part of our Twitter app we will build the MentionsViewController, which is pretty much the same code as we used before in Part II, but we will use a different Twitter API resource. So copy and paste the code from the PublicTimelineViewController part of the project into the MentionsViewController and we will fix the class names where appropriate.

MentionsViewController (2)

Are you ready for this? I hope you're a patient bunch - here we go.

Change this line in getFeed:

NSURL *feedURL = [NSURL URLWithString:@"http://api.twitter.com/1/statuses/home_timeline.json"];

To this:

NSURL *feedURL = [NSURL URLWithString:@"http://api.twitter.com/1/statuses/mentions.json"];

Voila! You're done. How cool is that? Web services are just awesome that way. It's very simple to simply reset your sights and you can hit another homerun, just like that.

ProfileViewController (3)

Let's downshift and relax our brains for a minute. We will do this by working on a simple view controller, the profile information. Open your storyboard and select your ProfileViewController scene in order to design something that looks like Figure A.

Figure A

We will use this to display our stats such as the number of tweets, favorites, followers etc. You can design the background any way you like; I just used some minimalist rectangles to differentiate between the sections of the view. Now let's move on to the code.

Open up the ProfileViewController.h and add this:

@property (weak, nonatomic) IBOutlet UILabel *usernameLabel;
@property (weak, nonatomic) IBOutlet UILabel *descriptionLabel;
@property (weak, nonatomic) IBOutlet UILabel *tweetsLabel;
@property (weak, nonatomic) IBOutlet UILabel *favoritesLabel;
@property (weak, nonatomic) IBOutlet UILabel *followingLabel;
@property (weak, nonatomic) IBOutlet UILabel *followersLabel;
Then in the ProfileViewController.m implement this:
@interface ProfileViewController ()
@end
@implementation ProfileViewController
@synthesize usernameLabel;
@synthesize descriptionLabel;
@synthesize tweetsLabel;
@synthesize favoritesLabel;
@synthesize followingLabel;
@synthesize followersLabel;
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
 return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

This will give us the objects we need to simply display the user's statistics.

Now we simply implement a private method to get the statistics with the right resource and populate our class objects. Once we implement the method (write it) we call it from a method called the viewWillAppear method, like so:

- (void)loadProfile{
 // 1. Store URL in a variable for later use
 NSURL *feedURL = [NSURL URLWithString:@"http://api.twitter.com/1/users/show.json"];
 // 2. Get reference to AppDelegate
 AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
 // 3. Get user account & set the label
 self.usernameLabel.text = appDelegate.userAccount.username;
 // 4. Create parameters dict
 NSDictionary *parameters = [NSDictionary dictionaryWithObjectsAndKeys:appDelegate.userAccount.username, @"screen_name", nil];
 // 5. Create Request & pass in dict & URL via GET
 TWRequest *twitterFeed = [[TWRequest alloc] initWithURL:feedURL parameters:parameters requestMethod:TWRequestMethodGET];
 // 6. Set twitter request's account to our appdelegate's
 // twitterFeed.account = appDelegate.userAccount;
 // 7. Add indicator on status bar
 UIApplication *sharedApplication = [UIApplication sharedApplication];
 sharedApplication.networkActivityIndicatorVisible = YES;
 // 8. Perform request
 [twitterFeed performRequestWithHandler:^(NSData *responseData,NSHTTPURLResponse *urlResponse,NSError *error) {
 if (!error) {
 //9. No errors? Put in object
 NSError *jsonError = nil;
 id feedData = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&jsonError];
 if (!jsonError) {
 NSDictionary *profileDictionary = (NSDictionary *)feedData;
 self.descriptionLabel.text = [profileDictionary valueForKey:@"description"];
 self.favoritesLabel.text = [[profileDictionary valueForKey:@"favourites_count"] stringValue];
 self.followersLabel.text = [[profileDictionary valueForKey:@"followers_count"] stringValue];
 self.followingLabel.text = [[profileDictionary valueForKey:@"friends_count"] stringValue];
 self.tweetsLabel.text = [[profileDictionary valueForKey:@"statuses_count"] stringValue];
 } else {
 // 9.1 If error parsing JSON display alert view error
 UIAlertView *alertView = [[UIAlertView alloc]
 initWithTitle:@"Error"
 message:[jsonError localizedDescription] delegate:nil
 cancelButtonTitle:@"OK" otherButtonTitles:nil];
 [alertView show];
 }
 } else {
 //9.2 If request unsuccessful display alert view with error
 UIAlertView *alertView = [[UIAlertView alloc]
 initWithTitle:@"Error" message:[error localizedDescription] delegate:nil
 cancelButtonTitle:@"OK" otherButtonTitles:nil];
 [alertView show];
 }
 // 10. Stop activity indicator
 sharedApplication.networkActivityIndicatorVisible = NO;
 }];
}

Now we simply call that method from one of our viewcontroller hook methods. They are called hook methods because they allow you to hook on to some event in the lifecycle of the viewcontroller to carry out a specific task.

-(void)viewWillAppear:(BOOL)animated{
 [self loadProfile];
}

The code is pretty well commented as before. The pattern again is the same as the previous ones. We are simply changing the resource used and of course, the information received from that resource.

FollowMeViewController (4)

In order to complete our Twitter Client, we wish to make it more than just a list of our tweets. Let's add some UX functionality starting with a FollowMe option.

Look in the Twitter API and you will find these resources:

  • GET friendships/exists
  • POST friendships/create
  • POST friendships/destroy
After you peruse those, head on over to your storyboard and design this look. (Figure B)

Figure B

FollowMeViewController

I simply dropped a button onto the viewcontroller scene, added an image in the right hand pane and filled in the text for that button. Now let's connect that button and create a method for it in the FollowMeViewController. Add this method to your FollowMeViewController .h:

@property (weak, nonatomic) IBOutlet UILabel *textLabel;
- (IBAction)followTapped:(id)sender;

Don't forget to connect the label to its outlet and its received action in storyboard.

Let's implement the .m file. First make the top of your .m look like this:

#import "FollowMeViewController.h"
#import "AppDelegate.h"
#import <Twitter/Twitter.h>
#define kAccountToFollow @"santiapps"
@interface FollowMeViewController ()
@property (assign) BOOL isFollowing;
- (void)checkFollowing;
@end

We need a Boolean to check and see if we are already following and a method to actually check and set the label. Don't forget to synthesize properties:

@synthesize textLabel = _textLabel;
@synthesize isFollowing = _isFollowing;

Again we need to restrict the orientation:

- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation{
 return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

Okay! Now we are ready to get into some Twitter API calls. First we want to check if we are already following a user or not. This is a big method, but you will notice it is very similar to the public timeline call:

- (void)checkFollowing{
 // 1. Create a variable for twitter
 NSURL *feedURL = [NSURL URLWithString:@"http://api.twitter.com/1/friendships/exists.json"];
 // 2. Get AppDelegate reference
 AppDelegate *appDelegate =[[UIApplication sharedApplication] delegate];
 // 3. Create NSDictionary for the TWR parameters
 NSDictionary *parameters =
 [NSDictionary dictionaryWithObjectsAndKeys: appDelegate.userAccount.username, @"screen_name_a", kAccountToFollow, @"screen_name_b", nil];
 // 4. Create TWRequest, with parameters, URL & specify GET method
 TWRequest *twitterFeed = [[TWRequest alloc] initWithURL:feedURL parameters:parameters requestMethod:TWRequestMethodGET];
 // 5. Specify twitter request's account to our app delegate's
 twitterFeed.account = appDelegate.userAccount;
 // 6. Show user activity is going on
 UIApplication *sharedApplication = [UIApplication sharedApplication]; sharedApplication.networkActivityIndicatorVisible = YES;
 // 7. Perform the TWR
 [twitterFeed performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
 if (!error) {
 NSString *responseString =
 [[NSString alloc] initWithBytes:[responseData bytes] length:[responseData length] encoding:NSUTF8StringEncoding];
 // 8. Set label's text to follower or not.
 if ([responseString isEqualToString:@"true"])
 {
 self.textLabel.text =
 @"Unfollow Santiapps";
 _isFollowing = YES; }
 else
 {
 self.textLabel.text =
 @"Follow Santiapps";
 _isFollowing = NO;
 }
 } else {
 // 9. If TWR unsuccessful, show alertview w/ error
 UIAlertView *alertView = [[UIAlertView alloc]
 initWithTitle:@"Error" message:[error localizedDescription] delegate:nil
 cancelButtonTitle:@"OK" otherButtonTitles:nil];
 [alertView show];
 }
 // 10. Stop activity indicator
 sharedApplication.networkActivityIndicatorVisible = NO;
 }];
}

This is a review so let's go a bit quickly:

  • We create a variable for the Twitter API URL in json format
  • Get a reference to the AppDelegate to be able to call on the user account
  • Create our dictionary of parameters for the Twitter call
  • Create the actual Twitter request
  • Specify the account
  • Display some activity for user to see
  • Perform the actual heavy lifting call
  • Set a label value to display if user is following
  • Pop an alert view if there was any error
  • Stop the UI Activity Indicator

Now simply call this method from your viewDidLoad by using:

[self checkFollowing];

Then we get into the actual action of what to do when the user taps on the follow button. Again, this will be a pretty quick review because as you can see the method structure is very similar:

- (IBAction)followTapped:(id)sender{
 // 1. Create url variable for json call
 NSURL *feedURL;
 // 2. Check to see if we need a follow or unfollow call
 if (_isFollowing) {
 feedURL = [NSURL URLWithString:@"http://api.twitter.com/1/friendships/destroy.json"];
 } else {
 feedURL = [NSURL URLWithString:@"http://api.twitter.com/1/friendships/create.json"];
 }
 // 3. Get a reference to the app delegate
 AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
 // 4. Create NSDictionary parameters.
 NSDictionary *parameters =
 [NSDictionary dictionaryWithObjectsAndKeys: @"true", @"follow",
 kAccountToFollow, @"screen_name", nil];
 // 5. Create TWRequest
 TWRequest *twitterFeed = [[TWRequest alloc] initWithURL:feedURL
 parameters:parameters requestMethod:TWRequestMethodPOST];
 // 6. Set user to our account
 twitterFeed.account = appDelegate.userAccount;
 // 7. Display UI activity indicator
 UIApplication *sharedApplication = [UIApplication sharedApplication]; sharedApplication.networkActivityIndicatorVisible = YES;
 // 8. Perform request
 [twitterFeed performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
 if (!error) {
 // 9. Set label value.
 if (!_isFollowing)
 {
 self.textLabel.text =
 @"Unfollow Santiapps";
 _isFollowing = YES;
 } else {
 self.textLabel.text =
 @"Follow Santiapps";
 _isFollowing = NO;
 }
 } else {
 // 10. Show error alertview if any
 UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error"
 message:[error localizedDescription] delegate:nil
 cancelButtonTitle:@"OK"
 otherButtonTitles:nil]; [alertView show];
 }
 // 11. Stop network activity indicator
 sharedApplication.networkActivityIndicatorVisible = NO;
 }];
}

All of this code is pretty similar as we have seen before and it is, as always, pretty well commented. So let's move on to a more exciting task.

TweetViewController

This viewcontroller class will have much the same structure as our original Twitter project from Part I but with a little sound, for fun.

Make your TweetViewController.h file look like this:

#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
@interface TweetViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *attachedLabel;
@property (weak, nonatomic) IBOutlet UITextField *statusTextField;
@property (weak, nonatomic) IBOutlet UILabel *successLabel;
- (IBAction)attachTapped:(id)sender;
- (IBAction)tweetTapped:(id)sender;
@end

Here we are simply creating a textfield, a label to display whether the tweet was successful, another label to display when something was attached. If you notice I also added an import for AudioToolbox. If you just add the import you will get an error. This is because we have not yet added the framework. So go to Target | Build Phases | Link Binary with Libraries and add the AudioToolbox framework to your project.

Let's turn our attention to the .m file and start if off like so:

#import "TweetViewController.h"
#import "AppDelegate.h"
#import <Twitter/Twitter.h>
@interface TweetViewController (){
 SystemSoundID sound1;
}
@property (assign) BOOL isAttached;
- (void)dismissKeyboard;
@end

We are importing the Twitter framework as always. In addition to that, we create an ivar for a system sound. Remember, it's an ivar because only this class needs to use it. We are also adding a method to dismissKeyboard, which you will use once you are done typing your tweet.

Add these methods to your implementation of TweetViewController:

-(BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation{
 return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)dismissKeyboard{
 // Dismiss keyboard if visible when user tapped the view.
 if (self.statusTextField.isFirstResponder) {
 [self.statusTextField resignFirstResponder];
 }
}

The important method here is the dismissKeyboard. We are basically saying if the text field (where we will write our tweet) is the first responder then have it resign as first responder. This method will be called from our tap method, which we will implement next. Thus, when the screen is tapped, if the textfield is first responder, it will resign, hiding the keyboard. This is a nice feature to have since users don't want to be looking for the Return or Enter button all the time. They can write their tweet and simply tap on the screen to do something else, like attach a file or something else.

Our viewDidLoad method is what gets things rolling:

- (void)viewDidLoad
{
 [super viewDidLoad];
 // Set attachment to NO, cause in a new tweet we wont have one yet
 _isAttached = NO;
 // Use tap gesture recognizer to dismiss keyboard
 UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissKeyboard)];
tapRecognizer.numberOfTapsRequired = 1; tapRecognizer.cancelsTouchesInView = NO;
 [self.view addGestureRecognizer:tapRecognizer];
}

This should seem fairly straightforward. We set our isAttached ivar to NO because we don't have an attachment when creating a new tweet. Then we create a UITapGestureRecognizer to call the dismissKeyboard method and set some of its properties. Finally we add the recognizer to the view it will reside in.

Now let's look at what happens when the attach button is tapped:

- (IBAction)attachTapped:(id)sender {
 // If image is attached, un-attach it, empty label and change our BOOL to NO. Otherwise we set attached label and BOOL to YES.
 if (_isAttached)
 {
 self.attachedLabel.text = @"";
 _isAttached = NO;
 } else {
 self.attachedLabel.text = @"Attachment";
 _isAttached = YES;
 }
}

Ok now on to the heavy lifting. However, this should be a breeze now that you have such good knowledge of this kind of method:

- (IBAction)tweetTapped:(id)sender { self.successLabel.text = @"";
 // 1. Create the URL variable
 NSURL *feedURL;
 // 2. If isAttached is true, use the update_with_media call, else regular update call.
 if (_isAttached)
 {
 feedURL = [NSURL URLWithString: @"https://upload.twitter.com/1/statuses/update_with_media.json"];
 }
 else
 {
 feedURL = [NSURL URLWithString: @"http://api.twitter.com/1/statuses/update.json"]; }
 // 3. Create our Tw Dictionary
 NSDictionary *parameters = [NSDictionary dictionaryWithObjectsAndKeys: self.statusTextField.text, @"status", @"true", @"wrap_links", nil];
 // 4. Create TWRequest
 TWRequest *twitterFeed = [[TWRequest alloc] initWithURL:feedURL parameters:parameters requestMethod:TWRequestMethodPOST];
 // 5. If isAttached then add it to our TWRequest
 if (_isAttached) {
 [twitterFeed addMultiPartData:UIImagePNGRepresentation([UIImage imageNamed:@"TweetImage.png"]) withName:@"media" type:@"image/png"];
[twitterFeed addMultiPartData:[self.statusTextField.text dataUsingEncoding:NSUTF8StringEncoding] withName:@"status" type:@"text/plain"];
 }
 // 6. Get the app delegate
 AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
 // 7. Set the TWRequest user account
 twitterFeed.account = appDelegate.userAccount;
 // 8. Enable indicator
 UIApplication *sharedApplication = [UIApplication sharedApplication]; sharedApplication.networkActivityIndicatorVisible = YES;
 // 9. Set  _isAttached BOOL to NO and attachedLabel
 _isAttached = NO;
 self.attachedLabel.text = @"";
 // 10.Perform TWRequest
 [twitterFeed performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
 if (!error)
 {
 self.successLabel.text = @"Tweeted Successfully";
 // 11.Placeholder J
 self.statusTextField.text = @"...";
 } else {
 // 12.In case of unsuccessful TwRequest, show alert view
 UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
 [alertView show];
 }
 // 13.Stop indicator
 sharedApplication.networkActivityIndicatorVisible = NO;
 }];
}

The idea here is to drill you with a not so different method so that you get used to the use of these method calls to see how and where to modify parameters.

Recap

So just to recap, we create an if/else test to use a different URL based on what we need to tweet. We then create the dictionary of parameters and the TWRequest as before. If there is an attachment we add it to the request. Again we get our Twitter account via the appDelegate. We set the local variables for attachment and label back to their original state. Finally we perform our Twitter request as before.

And then we add some sugar and spice:

-(void)playTweetSound{
 // also need an instance variable like so:
 // SystemSoundID sound1;
 NSLog(@"playingtweetsound");
 NSURL *soundURL = [[NSBundle mainBundle] URLForResource:@"Cardinal" withExtension:@"wav"];
 AudioServicesCreateSystemSoundID((__bridge CFURLRef)soundURL, &sound1);
 AudioServicesPlaySystemSound(sound1);
}

This method creates a sound object for us to play. Go ahead and run your app!

Wait, what, it didn't tweet!? Sorry, I did this on purpose to get your attention about something. When you are coding, you get caught up in designing what a method is supposed to do. Often times we forget to actually call the method itself and the console says nothing, because there is actually nothing wrong. So go back into the twitter request method and replace the line I labeled as //placeholder with this:

[self playTweetSound];

Voila! Now we have a complete Twitter client. Hope you enjoyed it. Don't forget to post your comments or any questions you might have.

Also read:

About

My name is Marcio Valenzuela and I am from Honduras. I have been coding in iOS for 5 years and I previously worked on web applications in ASP and PHP/mySQL. I have a few apps up in the app store and am currently working on a couple of Cocos2d games...

1 comments
Mark W. Kaelin
Mark W. Kaelin

The project outlined over the three parts is fairly involved - did you complete the project? Do you have questions?