Twitter Integration with Cocos2D and Twitter-OAuth-iPhone
If Facebook is the new email, then Twitter is the new... what? Text messaging? Micro-blogging? Or perhaps just simply a better way than either email or texting to keep your friends updated on your latest comings, goings and accomplishments. It's not too surprising then to see Twitter integration in games, especially iPhone games. One of the more popular integrations was Canabalt (http://www.canabalt.com) which used Twitter as it's exclusive global high-score service rather than the more typical Open Feint. For our game Sumo Tap, we wanted to use that as well:
With the addition of Twitter annotation "meta tags," which were recently announced at the Chirp developer conference (http://bit.ly/twitterannotations2010) I would expect more game specific features to be rolled out by Twitter developers and game developers as well. Simple tagging to identify high-score posts would be easy enough, but how about also sending links to game replays or trophy shots in the tweet as well?
Finally, as many have noted, Twitter has decide to change their authentication process (http://dev.twitter.com/announcements) and move everyone towards OAuth -- well at least for the RESTful API's: http://bit.ly/twcountdown
So, what are we to do for iPhone apps? One answer has been provided by Ben Gottlieb: http://github.com/bengottlieb/Twitter-OAuth-iPhone
OAuth
In short, OAuth is a more secure way for users to authorize applications to access their Twitter accounts. It currently is possible to use a simple dialog to request a users account and password and then post that (in the clear) to twitter.com but it's not a liability that I want for my application -- why should I be in the middle of a transaction between Twitter and my user? Just authorize my app, and let me get on with it. I don't want to broker any more information that I need.
OAuth on Twitter currently works by generating a token for each authorized application. This requires that you, as an iPhone app developer need to first register you app with Twitter.
Twitter API
The first thing you'll need to do is register you iPhone application with Twitter, which is easy enough to do here: http://dev.twitter.com/apps
You'll need to set your application type to "client" and the access type to "read & write" in order to work with Ben's library; see the screenshot below:
Twitter Application Keys
Once you register with Twitter, you'll be able to get you OAuth 1.0a keys from dev.twitter.com by going to "Your Apps" the selecting "Application Details" and look for the OAuth consumer key and secret key. You'll need both to get Ben's library working for you.
Twitter-OAuth-iPhone
In order to integrate Ben's library, you'll first need to start using git which you can install from the official Google code site, here: http://code.google.com/p/git-osx-installer
And then installing Ben's library (in the same directory as your iPhone app, if you wish) with a simple git command:
git git://github.com/bengottlieb/Twitter-OAuth-iPhone.git
XCode Integration with Cocos2D
Ben's library is a collection of three open source projects and so the challenging part can be getting the library settings to work correctly, which basically means you have to hunt through the different build requirements for each of the individual projects. Fortunately, the only challenge was limited to the MGTwitterEngine requirement for libxml2 library.
libxml2 dynamic library (from MTTwitterEngine readme)
-
Set USE_LIBXML to 1, near the top of the MGTwitterEngine.m file.
-
Add libxml2.dylib in Other Frameworks. You'll find the library in:
/usr/lib/libxml2.dylib
-
Add "/usr/include/libxml2" as a Header Search Path in your Project Settings.
The settings in my Xcode build look like the following:
Cocos2D Usage Pattern
So hopefully this will lead to a successful build of the Twitter-OAuth-iPhone library, but then how should you integrate into your Cocos2D application? For our Sumo Tap application, we're actually using the Xcode "Utility Application" template for our game. We found the easiest integration to be to place the Twitter login delegates into our applicationDelegate:
Then in our Cocos2D main scene, we create a CCMenu button with a "Twitter" CCMenuItem and in our callback function we simply call the main application delegate:
This allows us to put all of our < SA_OAuthTwitterControllerDelegate > logic in our app delegate, and a simple method to log into Twitter as well:
And the only change we had to make to the standard Twitter-OAuth delegate was to add a call to stop the Cocos2D animation in the SA_OAuthTwitterControllerDelegate like this:
Finally, here's what the post will look like on your Twitter homepage:
Just won 95 matches on Sumo Tap for iPhone from @playngive http://itunes.com/apps/sumotap
--yarri
Hi, I'm posting a follow-up to the article in answer to some questions in the comments... hopefully this post has better formatting.
1)Where is mainViewController initialized?
I'm using the design pattern of attaching cocos2d to an EAGLView, the EAGLView is controlled by the MainViewController and it's view is simply loaded from a nib file (MainView.xib) which I've taken from the XCode template for a utility application. It's initialized in the applicationDidFinishLaunching:
method like this:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
MainViewController *aController = [[MainViewController alloc] initWithNibName:@"MainView" bundle:nil];
self.mainViewController = aController;
[aController release];
...
2) What's the API call where you brag "Player X beat the pants off Player Y"
This is just a call to the sendUpdate:
method of the MGTwitterEngine. In my case, I have the following initialization:
// SA_OAuthTwitterEngine *_engine;
if (!_engine) {
_engine = [[SA_OAuthTwitterEngine alloc] initOAuthWithDelegate: self];
_engine.consumerKey = kOAuthConsumerKey;
_engine.consumerSecret = kOAuthConsumerSecret;
UIViewController *controller = [SA_OAuthTwitterController controllerToEnterCredentialsWithTwitterEngine: _engine delegate: self];
if (controller) {
// Something wrong happened... the twitter account should have been determined. Raise error dialog, handle error.
return;
}
else {
// Post message to users twitter account
[_engine sendUpdate:[NSString stringWithFormat:@"Player X beat the pants off Player Y"]];
}
}
--yarri
Reader Comments (14)
Hi,
Can you let me know what View Controller you used here and how you set it up? By default in Cocos2D you do not need it, so I wondered how you set it up and what effect it had on the rest of the application?
Thanks
Nalin
One other thing I forgot to ask was do you have the code for -(void) updateGlobalLeaderboard:(int)matches; because this seems to be missing?
Basically it would help to know what code you use to actually update the status :)
thanks
Nalin
Hi, maybe it's best to ask this question on the Cocos2d forums but basically we have a simple main window which is a standard EAGLView and there's a simple CCMenu in the main scene we calls the Twitter registration.
We use an NSNotification to poll for a call from within Cocos2d. And the application itself is the OAuth delegate as you can see from the first code listing above.
As far as updating the status, this is application specific. We have this embedded in our game logic after a winner is determined, we create an NSString with a simple brag message: "Player X beat the pants off Player Y and earned 10 points on their way to a Maigashira ranking on Sumo Tap for iPhone!"
--yarri
Thanks very much for responding Yarri
I would post on the Cocos Forum, but I have searched and you are the expert here :)
I hope you don't mind me asking, but I am just looking for a few lines of code:
1) Where is this initialised and how?
MainViewController *mainViewController;
2) What's the API call where you brag "Player X beat the pants off Player Y"
(I have never used MG Twitter)
Sorry if this is obvious to you. Hope you can help.
br
Nalin
Hi,
1)Where is mainViewController initialized?
I'm using the design pattern of attaching cocos2d to an EAGLView, the EAGLView is controlled by the MainViewController and it's view is simply loaded from a nib file (MainView.xib) which I've taken from the XCode template for a utility application. It's initialized in the
applicationDidFinishLaunching:
method like this:- (void)applicationDidFinishLaunching:(UIApplication *)application {
MainViewController *aController = [[MainViewController alloc] initWithNibName:@"MainView" bundle:nil];
self.mainViewController = aController;
[aController release];
...
2) What's the API call where you brag "Player X beat the pants off Player Y"
This is just a call to the
sendUpdate:
method of the MGTwitterEngine. In my case, I have the following initialization:// SA_OAuthTwitterEngine *_engine;
if (!_engine) {
_engine = [[SA_OAuthTwitterEngine alloc] initOAuthWithDelegate: self];
_engine.consumerKey = kOAuthConsumerKey;
_engine.consumerSecret = kOAuthConsumerSecret;
UIViewController *controller = [SA_OAuthTwitterController controllerToEnterCredentialsWithTwitterEngine: _engine delegate: self];
if (controller) {
// Something wrong happened... the twitter account should have been determined. Raise error dialog, handle error.
return;
}
else {
// Post message to users twitter account
[_engine sendUpdate:[NSString stringWithFormat:@"Player X beat the pants off Player Y"]];
}
}
--yarri
Thanks for this Yarri.
I managed to get it working to a degree, but I am trying to figure out how to avoid the user logging in each time. I notice you save off the Username once authentication has been established, but I do not know how you use this to login subsequently without the ViewController coming up.
Cheers
Nalin
Hi. I have followed your tutorial and got to a point where, when i tap the "Twitter" button, I see the Login UI for a fraction of a second than it disappears.
I implemented this code in a project created from the "cocos2d" template.
Any ideas why this happens?
In the twitterAccountLogin method, you have the following code...
controller = [SA_OAuthTwitterController controllerToEnterCredentialsWithTwitterEngine: _engine delegate: self];
A little further down, you then have
[controller release];
Isn't this releasing an autoreleased object and will therefore cause problems?
Thanks.
Hi,
I trying to implement this on a project created in unity and exported to iOS.
soo far good whit some errors.
i have implemented the FBConnect in that project too, works good.
but the Twitter dont, just cicles on this line
I notice some times works, but figured out only works after being launched the FBConnect first, i doesnt make sence in the example works perfect, any help will be apreciated.
here is the .h file
#import <UIKit/UIKit.h>
#import "SA_OAuthTwitterController.h"
@class SA_OAuthTwitterEngine;
@interface AppController : NSObject<UIAccelerometerDelegate, UIApplicationDelegate, SA_OAuthTwitterControllerDelegate>
{
UIWindow* _window;
SA_OAuthTwitterEngine *_engine;
}
- (void) startUnity:(UIApplication*)application;
- (void) startRendering:(UIApplication*)application;
@end
and the .mm file
#import "SA_OAuthTwitterEngine.h"
#define kOAuthConsumerKey @"5uXUkRWIYNBMLCi9Qe7FpQ" //REPLACE With Twitter App OAuth Key
#define kOAuthConsumerSecret @"YVUCnOr8aB3XLT7lGoEGcyw5q00IDQ8CBvFV01GtC0" //REPLACE With Twitter App OAuth Secret
#pragma mark Twitter implementation
- (void) twetMesage {
if(!_engine){
_engine = [[SA_OAuthTwitterEngine alloc] initOAuthWithDelegate:self];
_engine.consumerKey = kOAuthConsumerKey;
_engine.consumerSecret = kOAuthConsumerSecret;
}
UIViewController *controller = [SA_OAuthTwitterController controllerToEnterCredentialsWithTwitterEngine:_engine delegate:self];
if (controller){
[self presentModalViewController: controller animated: YES];
}
}
//=======================================================================================
#pragma mark SA_OAuthTwitterEngineDelegate
- (void) storeCachedTwitterOAuthData: (NSString *) data forUsername: (NSString *) username {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject: data forKey: @"authData"];
[defaults synchronize];
}
- (NSString *) cachedTwitterOAuthDataForUsername: (NSString *) username {
return [[NSUserDefaults standardUserDefaults] objectForKey: @"authData"];
}
//===============================================================================
#pragma mark TwitterEngineDelegate
- (void) requestSucceeded: (NSString *) requestIdentifier {
NSLog(@"Request %@ succeeded", requestIdentifier);
}
- (void) requestFailed: (NSString *) requestIdentifier withError: (NSError *) error {
NSLog(@"Request %@ failed with error: %@", requestIdentifier, error);
}
Hi, can you pass the completed code, because the function -(void) updateGlobalLeaderboard:(int)matches; i don't know how to do.
all i got, went post some phrase and post on twitter...
and i don`t know how to put "via "thegame" "
help please... thanks
Do you have some function to go BACK to MenuScene?
thanks
Owesome man !!! you did a great tutorial, thanks
Hello,
I am using Xcode 4 and xmlparser version libxml2.2.dylib. I have added the Header search Path as all you described here and also added the dynamic parser. But still I am having the same problem.
The error says:
ld: warning: ignoring file /Users/afanurrashid/Desktop/TwitterTest/libxml2.2.dylib, missing required architecture i386 in file
Undefined symbols for architecture i386:
"_xmlReaderForMemory", referenced from:
-[MGTwitterLibXMLParser initWithXML:delegate:connectionIdentifier:requestType:responseType:URL:] in MGTwitterLibXMLParser.o
"_xmlTextReaderIsEmptyElement", referenced from:
-[MGTwitterLibXMLParser _nodeValue] in MGTwitterLibXMLParser.o
-[MGTwitterLibXMLParser _statusDictionaryForNodeWithName:] in MGTwitterLibXMLParser.o
-[MGTwitterLibXMLParser _userDictionaryForNodeWithName:] in MGTwitterLibXMLParser.o
-[MGTwitterLibXMLParser _hashDictionaryForNodeWithName:] in MGTwitterLibXMLParser.o
-[MGTwitterMessagesLibXMLParser _directMessageDictionaryForNodeWithName:] in MGTwitterMessagesLibXMLParser.o
................................
.....................
"_xmlFree", referenced from:
-[MGTwitterLibXMLParser initWithXML:delegate:connectionIdentifier:requestType:responseType:URL:] in MGTwitterLibXMLParser.o
-[MGTwitterLibXMLParser _nodeValueAsString] in MGTwitterLibXMLParser.o
-[MGTwitterLibXMLParser _nodeValueAsDate] in MGTwitterLibXMLParser.o
-[MGTwitterLibXMLParser _nodeValueAsInt] in MGTwitterLibXMLParser.o
-[MGTwitterLibXMLParser _nodeValueAsBool] in MGTwitterLibXMLParser.o
ld: symbol(s) not found for architecture i386
collect2: ld returned 1 exit status
Can anyone help me in this regard. I am having great problem with this error.
Thanks in advance.
I have done everything what you have described but it seems that i may be doing some mistake. for this reason it says i am authenticated but my tweet is not posted in my wall.
Here is my appsDelegate.h
#import <UIKit/UIKit.h>
#import "SA_OAuthTwitterController.h"
@class SA_OAuthTwitterEngine;
@class RootViewController;
@interface AppDelegate : NSObject <UIApplicationDelegate, SA_OAuthTwitterControllerDelegate> {
UIWindow *window;
RootViewController *viewController;
SA_OAuthTwitterEngine *_engine;
}
@property (nonatomic, retain) UIWindow *window;
-(void)twitterAccountLogin;
@end
My appsDelegate.m file:
#import "cocos2d.h"
#import "AppDelegate.h"
#import "GameConfig.h"
#import "HelloWorldLayer.h"
#import "RootViewController.h"
#import "SA_OAuthTwitterEngine.h"
#import "SA_OAuthTwitterController.h"
#define kOAuthConsumerKey @"e7MGLzDySl8LPqKrunFt6Q"
#define kOAuthConsumerSecret @"Rg2r0Zntzho4B4Mvv6DKKBEK7TBwFjmlgX8Zz2DlocQ"
.......
.......
-(void)twitterAccountLogin {
if (_engine) {
return;
}
_engine = [[SA_OAuthTwitterEngine alloc] initOAuthWithDelegate: self];
_engine.consumerKey = kOAuthConsumerKey;
_engine.consumerSecret = kOAuthConsumerSecret;
UIViewController *controller = [SA_OAuthTwitterController controllerToEnterCredentialsWithTwitterEngine: _engine delegate: self];
if (controller)
{
NSLog(@"Unable to post tweet");
[viewController presentModalViewController: controller animated: YES];
}
else {
NSLog(@"I am in the Send Update Method");
[_engine sendUpdate: [NSString stringWithFormat: @"Player X beat the pants off Player Y"]];
}
}
From the NSLog It shows that i am in the if(controller) section which shouldn't be.
Where is my Problem. If anyone wants I can send him the complete code....
Please help me....