A Twitter reusable class. iOS 5.0 ready with iOS 4.x retrocompatibility

Today I would like to share with you a little but useful class to post highscores  to Twitter on your games. This tutorial is the perfect companion for the Facebook one I wrote some weeks ago. It basically adds a new abstraction layer to the MGTwitterEngine from Matt Gemmel and the OAuth Twitter Engine from Ben Gottlieb providing the needed UI to send tweets to the user timeline.

The tutorial also shows how to figure out the iOS version and use the Apple’s Twitter API for iOS 5 if available.

You can download the entire project at the end of the article.

So, the first thing we need to do is to manage to make the OAuth Twitter Engine compile and work. To avoid being redundant,  here you have a very nice tutorial that guides you step-by-step on this task. We are going to “override” the lasts steps of this tutorial. Come back here when you are ready 🙂

So, if everything went ok, we now have a Xcode project with all the necessary libraries to connect to Twitter and send tweets to the user’s timeline. Let’s add the abstraction layer to easily send new high scores for our games taking into account the iOS version and using the new iOS 5 Twitter API if available.

The TwitterViewController

First, we are going to create a custom UIViewController that will use the OAuth Twitter Engine. So go ahead and create a new file of the type UIViewController. Remember to also create the corresponding XIB file.

Add to the XIB file the following elements in the UI editor (previously known as Interface Builder :] ):

  • A navigation bar
  • A “Cancel” button on the left hand of the navigation bar
  • A “Send” button on the right hand of the navigation bar
  • A text field to show and modify the tweet text
  • A label to count the current number of characters of the tweet text
TwitterViewController

TwitterViewController

Add the corresponding Outlets (remember to synthesize them):

// Outlet declaration
IBOutlet UINavigationBar *navigationBar;
IBOutlet UIBarButtonItem *buttonCancel;
IBOutlet UIBarButtonItem *buttonSend;
IBOutlet UITextView *textView;
IBOutlet UILabel *labelCharsCount;

// Properties
@property (nonatomic, retain) IBOutlet UINavigationBar *navigationBar;
@property (nonatomic, retain) IBOutlet UIBarButtonItem *buttonCancel;
@property (nonatomic, retain) IBOutlet UIBarButtonItem *buttonSend;
@property (nonatomic, retain) IBOutlet UITextView *textView;
@property (nonatomic, retain) IBOutlet UILabel *labelCharsCount;

To complete our TwitterViewController.h file we need a reference to SA_OauthTwitterEngine, a NSString to store the suggested tweet we are going to send and some imports, protocols and actions. Here you have the complet listing for TwitterViewController.h:

#import <UIKit/UIKit.h>
#import "SA_OAuthTwitterController.h"
#import "MBProgressHUD.h"

#define kOAuthConsumerKey @"" // REPLACE
#define kOAuthConsumerSecret @"" // REPLACE

#define kAlertDelayTime 2.0
#define kTweetLenght 140

@class SA_OAuthTwitterEngine;

@interface TwitterViewController : UIViewController <SA_OAuthTwitterControllerDelegate, MBProgressHUDDelegate> {
SA_OAuthTwitterEngine *_engine;
MBProgressHUD *progressHUD;

IBOutlet UINavigationBar *navigationBar;
IBOutlet UIBarButtonItem *buttonCancel;
IBOutlet UIBarButtonItem *buttonSend;
IBOutlet UITextView *textView;
IBOutlet UILabel *labelCharsCount;

NSString *tweetSuggested;
}

@property (nonatomic, retain) IBOutlet UINavigationBar *navigationBar;
@property (nonatomic, retain) IBOutlet UIBarButtonItem *buttonCancel;
@property (nonatomic, retain) IBOutlet UIBarButtonItem *buttonSend;
@property (nonatomic, retain) IBOutlet UITextView *textView;
@property (nonatomic, retain) IBOutlet UILabel *labelCharsCount;

@property (nonatomic, retain) NSString *tweetSuggested;

- (IBAction)buttonCancelTouchUp:(id)sender;
- (IBAction)buttonSendTouchUp:(id)sender;

-(void)textChanged:(NSNotification*)notification;

@end

Let’s take a look to this header file.

On line 3 you probably have noticed a weird import. We are going to use this nice class to show alert information to the user about operation success and error. So let’s download the MBProgressHUD classes and add them to the project.

On lines 5 and 6 we have the twitter dev information needed to connect to the Twitter API. If you still don’t have this information follow this tutorial.

Notice the protocols we are conforming to. We will need to implement the corresponding methods on the TwitterViewController.m file.

TwitterViewController implementation

The implementation file for TwitterViewController basically contains the needed methods to manage the UI states and animations. Actually, the hard work is done in the OAuth Twitter Engine we are using at the back stage.

Let me add first some private utility methods at the beginning of the TwitterViewController.m file:

#pragma mark - Private Methods

-(void) delayedDismiss {
[self dismissModalViewControllerAnimated:YES];
}

-(void) enableSend {
labelCharsCount.textColor = [UIColor blackColor];
buttonSend.enabled = YES;
}

-(void) disableSend {
labelCharsCount.textColor = [UIColor redColor];
buttonSend.enabled = NO;
}

-(void) showUI {
navigationBar.topItem.title = [NSString stringWithFormat:@"@%@", _engine.username];
buttonCancel.enabled = YES;
buttonSend.enabled = YES;
textView.hidden = NO;
labelCharsCount.hidden = NO;

int charsLeft = kTweetLenght - textView.text.length;
if (charsLeft < 0) {
[self disableSend];
} else {
[self enableSend];
}
}

-(void) hideUI {
navigationBar.topItem.title = NSLocalizedString(@"", nil);
buttonCancel.enabled = NO;
buttonSend.enabled = NO;
textView.hidden = YES;
labelCharsCount.hidden = YES;
}

We will use a performSelector method to dismiss the TwitterViewController after a while when a tweed has been sent or the twitter authentication is cancelled. We need to disable the “Send” button when the user exceeds the 140 characters. Finally, we will need to hide the entire UI if the user isn’t already logged in to Twitter.

Now, let’s take a look at the initialization methods:

-(void) initLabels {
buttonCancel.title = NSLocalizedString(@"Cancel", nil);
buttonSend.title = NSLocalizedString(@"Send", nil);
}

- (void)viewDidLoad {
// Set notification for text view text change
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];

// Hide UI
[self hideUI];

// Init labels
[self initLabels];

// Set the corresponding suggested tweet
textView.text = tweetSuggested;

// Init the chars left for suggested tweet.
int charsLeft = kTweetLenght - textView.text.length;
labelCharsCount.text = [NSString stringWithFormat:@"%d", charsLeft];
}

- (void) viewDidAppear: (BOOL)animated {
[super viewDidAppear:animated];

[textView becomeFirstResponder];

if (_engine) {

} else {
_engine = [[SA_OAuthTwitterEngine alloc] initOAuthWithDelegate: self];
_engine.consumerKey = kOAuthConsumerKey;
_engine.consumerSecret = kOAuthConsumerSecret;

UIViewController *controller = [SA_OAuthTwitterController controllerToEnterCredentialsWithTwitterEngine: _engine delegate: self];
controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
controller.modalPresentationStyle = UIModalPresentationFormSheet;

if (controller)
[self presentModalViewController: controller animated: YES];
else {
[self showUI];
}
}
}

On “viewDidLoad” we first set a notification observer to track the text view text changes. We will use it to update properly the label that counts the current tweet characters and the “Send” button enable/disable state.

Then, hide the UI, init some labels (for localization purposes), init the textView text with the suggested text (which is initialized by the client class) and init the chars counter label.

On “viewDidAppear:” we first make the textView become the first responder to force the virtual keyboard to appear. Then we init the OAuth Twitter Engine if needed and prompt the user to log in if needed.

Ok, now we need to add the OAuth Twitter Engine delegate methods:

#pragma mark - SA_OAuthTwitterEngineDelegate
- (void) storeCachedTwitterOAuthData: (NSString *) data forUsername: (NSString *) username {
NSLog(@"storing twitter login: %@", username);

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

[defaults setObject: data forKey: @"authData"];
[defaults synchronize];

NSLog(@"cached login data: %@", data);
}

- (NSString *) cachedTwitterOAuthDataForUsername: (NSString *) username {
NSLog(@"twitter login: %@", username);

return [[NSUserDefaults standardUserDefaults] objectForKey: @"authData"];
}

#pragma mark SA_OAuthTwitterControllerDelegate
- (void) OAuthTwitterController: (SA_OAuthTwitterController *) controller authenticatedWithUsername: (NSString *) username {
NSLog(@"Authenicated for %@", username);

[self showUI];
}

- (void) OAuthTwitterControllerFailed: (SA_OAuthTwitterController *) controller {
NSLog(@"Authentication Failed!");
}

- (void) OAuthTwitterControllerCanceled: (SA_OAuthTwitterController *) controller {
NSLog(@"Authentication Canceled.");

[self performSelector:@selector(delayedDismiss) withObject:nil afterDelay:0.7];
}

#pragma mark TwitterEngineDelegate
- (void) requestSucceeded: (NSString *) requestIdentifier {
NSLog(@"Request %@ succeeded", requestIdentifier);

progressHUD.customView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"check-mark.png"]] autorelease];
progressHUD.mode = MBProgressHUDModeCustomView;
progressHUD.labelText = NSLocalizedString(@"Operation Successful", nil);

[progressHUD hide:YES afterDelay:kAlertDelayTime];
[self performSelector:@selector(delayedDismiss) withObject:nil afterDelay:kAlertDelayTime];
}

- (void) requestFailed: (NSString *) requestIdentifier withError: (NSError *) error {
NSLog(@"Request %@ failed with error: %@", requestIdentifier, error);

progressHUD.customView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"X-mark.png"]] autorelease];
progressHUD.mode = MBProgressHUDModeCustomView;
progressHUD.labelText = NSLocalizedString(@"An Error Occured", nil);
[progressHUD hide:YES afterDelay:kAlertDelayTime];

}

A lot of lines of code but only doing trivial things 🙂 Basically, we use the delegate methods to manage the state of the UI (here we use the MBProgressHUD class) and to store the user credentials to avoid him to re-introduce them on every launch of the game.

We are almost done with the TwitterViewController class 🙂 We only need to add the action methods that will make our UI responsive 😀

#pragma mark - Actions

- (IBAction)buttonCancelTouchUp:(id)sender {
[self dismissModalViewControllerAnimated:YES];
}

- (IBAction)buttonSendTouchUp:(id)sender {
[_engine sendUpdate: textView.text];

// Progress HUD
progressHUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:progressHUD];
progressHUD.delegate = self;
CGRect frame = CGRectMake(progressHUD.frame.origin.x, progressHUD.frame.origin.y-50, progressHUD.frame.size.width, progressHUD.frame.size.height);
progressHUD.frame = frame;
[progressHUD show:YES];
}

-(void)textChanged:(NSNotification*)notification {
int charsLeft = kTweetLenght - textView.text.length;
labelCharsCount.text = [NSString stringWithFormat:@"%d", charsLeft];

if (charsLeft < 0) {
[self disableSend];
} else {
[self enableSend];
}
}
TwitterViewController in action

TwitterViewController in action

Ok, now we have a TwitterViewController class that allows us to present a view controller that allows the user to send a tweet to his timeline. However, we would like to take advantage of the new Apple’s Twitter API. Moreover, it would be useful for our needs that the interface of this Twitter module was more abstract, with methods specifically designed for high scores or gaming purposes.

So, let’s create a TwitterHelper class :]

The TwitterHelper singleton

We are going to create a singleton class that will manage the iOS version issues and will also expose a more abstract API dealing with the high scores concept we are interested on.

Here you have the TwitterHelper.h file:

#import

#define kAppName @"My Game Name"
#define kServerLink @"http://www.refractedpixel.com"
#define kImageSrc @"http://bit.ly/tBnTMM"
#define kCustomMessage @"I just got a score of %d in %@, an amazing iOS game! %@ %@"

@interface TwitterHelper : NSObject {

}

+ (TwitterHelper *) sharedInstance;

#pragma mark - Public Methods

-(void) presentSendTweetSheetOnViewController:(UIViewController*)viewController withNewHighScore:(int)newNighScore;

@end

Here we define high level constants referring to the name of our game, the official page, an image to attach to the tweet and the tweet text.

We only have one method for this tutorial but you could define here all the high level methods you need for your own games. Let’s have a look at the implementation of the method “presentSendTweetSheetOnViewController: withNewHighScore: “:

@implementation TwitterHelper

#pragma mark - Private Methods

-(void) presentSendTweetSheetOnViewController:(UIViewController*)viewController withInitialText:(NSString*)initialText {
CGFloat osVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if (osVersion >= 5.0) {
if ([TWTweetComposeViewController canSendTweet]) {
TWTweetComposeViewController *tweetSheet = [[TWTweetComposeViewController alloc] init];
[tweetSheet setInitialText:initialText];

[viewController presentModalViewController:tweetSheet animated:YES];

} else {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Can't send tweet"
message:@"You can't send a tweet right now. Make sure your device has an internet connection and you have at least one Twitter account setup"
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alertView show];
}

} else {
TwitterViewController *twitterView = [[TwitterViewController alloc] initWithNibName:@"TwitterViewController" bundle:nil];
twitterView.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
twitterView.modalPresentationStyle = UIModalPresentationFormSheet;
twitterView.tweetSuggested = initialText;
[viewController presentModalViewController:twitterView animated:YES];
[twitterView release];
}
}

#pragma mark - Public Methods

-(void) presentSendTweetSheetOnViewController:(UIViewController*)viewController withNewHighScore:(int)newNighScore {
NSString *initialText = [NSString stringWithFormat:kCustomMessage, newNighScore, kAppName, kServerLink, kImageSrc];
[self presentSendTweetSheetOnViewController:viewController withInitialText:initialText];
}

@end

So, what we basically do is to check the current iOS version and if it is 5.0 or higher we use the new Apple’s Twitter API. Otherwise, we alloc and init an instance of the TwitterViewController and present it modally on the view controller that the client has given as a method parameter.

Notice that as we are using a framework that is not available on iOS versions prior to 5.0 we need to add the twitter framework as optional:

Add the Twitter framework as optional

Add the Twitter framework as optional

And the TwitterHelper is ready! 😉

Usage

Finally! That was a hard work, isn’t it? :] The good news are that to use this twitter module for posting high scores in all your games you only need a single line of code!

[[TwitterHelper sharedInstance] presentSendTweetSheetOnViewController:self withNewHighScore:score];

That’s it! 😀

Modifications on the Engine

If you download the project provided in this article you may notice subtle differences in the OAuth Twitter Engine. I have slightly modified the engine to make it compatible with the iPad.

Conclusion

You can download the full Xcode project here. This code will probably be useful only for a few months. However, currently it is not rare that you need/want to keep compatibility with iOS versions prior to 5.0 while giving your 5.0 users the best user experience with the new Apple’s Twitter API. In that case, hope this helps :]

This post is part of iDevBlogADay, a group of indie iOS development blogs. You can keep up with iDevBlogADay through the web siteRSS feed, or Twitter.

4 thoughts on “A Twitter reusable class. iOS 5.0 ready with iOS 4.x retrocompatibility

  1. Dear Toni,

    I tried to install the TwitterSupportDemo project on iOS 4.x, but i am not able to install it. Can you give me any idea. When I run the app, after successfully building the app, it’s getting stopped. I hope that you will help me.

    Regards,
    Sreelash

  2. I think Speelash saw this

    2012-05-08 14:46:20.962 TwitterSupportDemo[1672:b903] twitter login: (null)
    2012-05-08 14:46:20.963 TwitterSupportDemo[1672:b903] *** Assertion failure in -[SA_OAuthTwitterEngine consumer], /Users/user/Downloads/TwitterSupport/TwitterSupportDemo/TwitterSupport/Twitter+OAuth/SAOAuthTwitterEngine/SA_OAuthTwitterEngine.m:80
    2012-05-08 14:46:20.966 TwitterSupportDemo[1672:b903] CoreAnimation: ignoring exception: You must first set your Consumer Key and Consumer Secret properties. Visit http://twitter.com/oauth_clients/new to obtain these.

    This means you not add your keys to

    TwitterViewController.m

    right here

    #define kOAuthConsumerKey @”” // REPLACE
    #define kOAuthConsumerSecret @”” // REPLACE

    Thanks, to guys from http://midatlanticconsulting.com who help me find out this my mistake

Leave a Reply