How To Integrate Cocos2D and UIKit

Cocos2D is a great framework, but sometimes it’s handy to implement some of your game with UIKit. For example, it’s often useful to design your main menu, settings pages, and the like with UIKit and just use Cocos2D for your main game logic. You also might find it handy to use UIKit controls on top […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Communicating with the RootViewController

You often need to have a way to communicate to the RootViewController from Cocos2D. For example, you might want to display a UI element upon some event. In our case, we want to show or hide the HUD panel on a double tap.

There are multiple ways to do this to avoid dependencies, but the easiest way is to just pass a parameter to the RootViewController to the layer directly.

Open up HelloWorldLayer.h and make the following changes:

// Add to top of file
#import "RootViewController.h"

// Add inside @interface
RootViewController * _rootViewController;

// Replace sceneWithCalendar and initWithCalendar with these
+ (CCScene *) sceneWithCalendar:(int)lastCalendar rootViewController:(RootViewController *)rootViewController;
- (id)initWithCalendar:(int)lastCalendar rootViewController:(RootViewController *)rootViewController;

Then switch to HelloWorldLayer.m and make the following changes:

// Replace sceneWithCalendar method with the following
+(CCScene *) sceneWithCalendar:(int)calendar rootViewController:(RootViewController *)rootViewController 
{
    CCScene *scene = [CCScene node];
    HelloWorldLayer *layer = [[[HelloWorldLayer alloc] 
                               initWithCalendar:calendar rootViewController:rootViewController] autorelease]; // new
    [scene addChild: layer];	
    return scene;
}

// Replace beginning of initWithCalendar with the following
-(id) initWithCalendar:(int)calendar rootViewController:(RootViewController *)rootViewController
{
	if( (self=[super init])) {
                
        _rootViewController = rootViewController;
        // Rest of method...

// Replace touch handler methods with the following
- (void)handleTap:(UITapGestureRecognizer *)tapRecognizer {
    CCLOG(@"Tap!");
    CCScene *scene = [HelloWorldLayer sceneWithCalendar:calendarNum+1 rootViewController:_rootViewController];
    [[CCDirector sharedDirector] replaceScene:[CCTransitionSlideInR transitionWithDuration:0.25 scene:scene]];
}

- (void)handleDoubleTap:(UITapGestureRecognizer *)doubletapRecognizer {
    CCLOG(@"Double Tap!");    
    [_rootViewController toggleUI];
}

- (void)handleLeftSwipe:(UISwipeGestureRecognizer *)swipeRecognizer {
    CCLOG(@"Swipe Left!");
    CCScene *scene = [HelloWorldLayer sceneWithCalendar:calendarNum+1 rootViewController:_rootViewController];
    [[CCDirector sharedDirector] replaceScene:[CCTransitionSlideInR transitionWithDuration:0.25 scene:scene]];    
}

- (void)handleRightSwipe:(UISwipeGestureRecognizer *)swipeRecognizer {
    CCLOG(@"Swipe Right!");
    CCScene *scene = [HelloWorldLayer sceneWithCalendar:calendarNum-1 rootViewController:_rootViewController];
    [[CCDirector sharedDirector] replaceScene:[CCTransitionSlideInL transitionWithDuration:0.25 scene:scene]];    
}

So basicaly we just pass a reference to the RootViewController through – pretty simple stuff. And on double tap, we call a new method called toggleUI that we’ll write now!

First, we need to add some outlets for the buttons so we can show/hide them. Open up RootViewController.xib, and control-drag from the home button into the assistant editor’s RootViewController.h, right after the @interface. Set the connection to Outlet, set the name to homeButton, and click connect.

Connecting the home button

Repeat for the mail button, naming the outlet mailButton.

Next, open up RootViewController.h and make the following changes:

// Add inside @interface
BOOL _showingUI;
CGPoint _mailCenterOrig;
CGPoint _homeCenterOrig;

// Add after @interface
- (void)toggleUI;

Then open RootViewController.m and make the following changes:

// Add inside viewDidLoad, BEFORE call to setupCocos2D
_showingUI = YES;
_mailCenterOrig = _mailButton.center;
_homeCenterOrig = _homeButton.center;

// Modify call to sceneWithCalendar inside setupCocos2D with the following
CCScene *scene = [HelloWorldLayer sceneWithCalendar:1 rootViewController:self];

// Add after viewDidLoad
- (void)toggleUI {
    _showingUI = !_showingUI;
    
    [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^(void) {        
        if (_showingUI) {
            _mailButton.center = _mailCenterOrig;
            _homeButton.center = _homeCenterOrig;
        } else {
            _mailButton.center = CGPointMake(_mailCenterOrig.x+_mailButton.bounds.size.width, _mailCenterOrig.y+_mailButton.bounds.size.height);
            _homeButton.center = CGPointMake(_homeCenterOrig.x-_homeButton.bounds.size.width, _homeCenterOrig.y+_homeButton.bounds.size.height);
        }
    } completion:^(BOOL finished) {
    }];
}

This uses UIView animation to make a nice animation to make the buttons slide offscreen/onscreen to show and hide them. If you’re new to UIView animation, you might want to check out the How To Use UIView Animation Tutorial.

That’s it – compile and run, and you should be able to double tap the screen to reveal the wallpapers in their full glory!

Fullscreen wallpaper with UIKit elements offscreen

Communicating with the Layer

Just like you sometimes need to communicate to the RootViewController from the layer, you also often need to communicate with the layer from the RootViewController.

We’re going to implement tapping the mail button to send an email with the current wallpaper. The RootViewController will contain the code to present the mail composer, but it will need to ask the layer for an UIImage of the currently displayed wallpaper.

Again, there are multiple ways to handle this, but we’re going to take the simple way – just ask the CCDirector for the current running scene. We only have one scene/layer, so we can just get the first layer in the scene and cast it to a HelloWorldLayer, then call whatever methods we want on it.

Let’s first prepare the HelloWorldLayer to have a method to give us the current wallpaper image. Open HelloWorldLayer.h and make the following changes:

// Add inside @interface
UIImage * _curImage;

// Add after @interface
@property (retain) UIImage * curImage;

Then switch to HelloWorldLayer.m and make the following changes:

// Add after @implementation
@synthesize curImage = _curImage;

// Inside maskedSpriteWithBuffer, comment out these lines:
//[textureSprite setBlendFunc:(ccBlendFunc){GL_DST_ALPHA, GL_ZERO}];
//[maskSprite visit];

// Add inside maskedSpriteWithBuffer, right before return retval
self.curImage = [rt getUIImageFromBuffer];

// Add to dealloc
[_curImage release];
_curImage = nil;

Here we use a handy method on CCRenderTexture to convert the current buffer to a UIImage. We also remove the call to visiting the mask sprite, since we don’t really need to demonstrate this anymore, so we’ll just look at the full-screen wallpapers.

Next, open RootViewController.xib, and control-drag from the mail button below the @interface in the assistant editor’s RootViewController.h. Set the connection type to Action, the name to mailTapped, and click Connect.

Connecting the mail action

To display the mail composer, we need to add the MessageUI framework to the project. So select the MaskedCal project in the groups & files tree, select the MaskedCal target, select the Build Phases Tab, expand the Link binary with Libraries section, and click the + button.

Adding a framework in Xcode 4

Select MessageUI.framework from the dropdown and click Add. Then open RootViewController.h and make the following changes:

// Add to top of file
#import <MessageUI/MessageUI.h>
#import <MessageUI/MFMailComposeViewController.h>

// Mark class as implementing MFMailComposeViewControllerDelegate
@interface RootViewController : UIViewController <MFMailComposeViewControllerDelegate> {

And finally, switch to RootViewController.m and replace the mailTapped method with the following (plus two new methods):

- (void)mailData:(NSData *)data {
    if (![MFMailComposeViewController canSendMail]) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Oops!", @"")
                                                        message:NSLocalizedString(@"Your device cannot send mail.", @"") 
                                                       delegate:self 
                                              cancelButtonTitle:NSLocalizedString(@"OK", @"")
                                              otherButtonTitles:nil];
        [alert show];
        [alert release];
        return;
    }
        
	// Start up mail picker
	MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
	
	UINavigationBar *bar = picker.navigationBar;
	picker.mailComposeDelegate = self;
	
	[picker setSubject:@"Check out this cute wallpaper!"];
	[picker addAttachmentData:data mimeType:@"image/jpg" fileName:@"wallpaper.jpg"];
	
	// Set up the recipients.
	NSArray *toRecipients = [NSArray arrayWithObjects:nil];	
	[picker setToRecipients:toRecipients];
	
	// Fill out the email body text.
    NSString *actualBody = @"Check out this cute wallpaper!  You can download the fullscreen version for free from: http://www.vickiwenderlich.com";
	[picker setMessageBody:actualBody isHTML:NO];
	
	// Present the mail composition interface.
	[self presentModalViewController:picker animated:YES];
	
	bar.topItem.title = @"Email Wallpaper";
	
	[picker release]; // Can safely release the controller now.
}

- (IBAction)mailTapped:(id)sender {
       
    CCScene * scene = [[CCDirector sharedDirector] runningScene];
    HelloWorldLayer *layer = [scene.children objectAtIndex:0];
    UIImage *curImage = layer.curImage;
    NSData *data = UIImageJPEGRepresentation(curImage, 0.8); 
    
    [self mailData:data];
    
}

#pragma mark MFMailComposeViewControllerDelegate

- (void)mailComposeController:(MFMailComposeViewController *)controller
		  didFinishWithResult:(MFMailComposeResult)result
						error:(NSError *)error {
    [self dismissModalViewControllerAnimated:YES];
}

Notice how the mailTapped method gets the running scene, pulls out the first child, and knows that it is a HelloWorldLayer. Then it can get the curImage from the layer.

Compile and run, and now you can send a wallpaper you like to a friend! :]

Emailing a wallpaper with MFMailViewController

Contributors

Over 300 content creators. Join our team.