Browsing articles tagged with " UIScrollView"

Cocos2D and UIScrollView

Aug 21, 2009   //   by Rob Segal   //   Development  //  127 Comments

Learning now to work with a new framework is usually a challenge.  You often delve into a new API or SDK expecting things to work in a certain manner.  When those expectations fail you need to delve deeper to find your answer in several different ways.  It often starts with visiting the website of the package you are working with.  This often leads to..

All of these approaches have their pros and cons and I have often found its some combination of these sources that will lead you to your answer.  In my case I have been trying over the past couple of weeks to get Cocos2D working with a UIScrollView container.  This is a requirement for the game we are working on to allow players to get the familiar scrolling movement you see in many iPhone apps.

We tried a number of approaches to get this to work properly…

  1. Cocos window embedded inside a UIScrollView instance
  2. Changing the size of the OpenGL rendering window to render a larger space.  Works but there is a serious performance hit.
  3. A UIScrollView instance overlaid on top of a Cocos window.  If the scroll movements were caught they could be passed onto the Cocos window manually.

Ultimately we chose option #3 and this is the option I will cover details for in this article as it is ultimately the only solution which worked for us properly.

Modifications to the Cocos source code

The first step to get this to work properly is a small modification to the Cocos source code.  The reason being that Cocos will freeze while working with a UIScrollView by default.  Luckily the location to you need to modify is noticeably documented in the Cocos source code.  In Director.m look for this block of comment with code…

//
// If you want to attach the opengl view into UIScrollView
// uncomment this line to prevent 'freezing'. It doesn't work on
// with the Fast Director
//
// [[NSRunLoop currentRunLoop] addTimer:animationTimerforMode:NSRunLoopCommonModes];

Simply uncomment this line of code to get freeze free scrolling.  Leave the lines commented out to see the difference in movement as my description may not make it readily apparent.

EDIT: If you are using Cocos 0.99.4 or later use the following directions provided by one of our helpful community members “drootang” for solving the freezing issue…


I’m using 0.99.4 and it took me a while to figure out that the new hello world uses the following line in applicationDidFinishLaunching:

CC_DIRECTOR_INIT();

This macro (among other things) does the following:

if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )
[CCDirector setDirectorType:kCCDirectorTypeNSTimer];

You need to force it to set the director type to kCCDirectorTypeNSTimer to prevent cocos2d from “freezing” while scrolling. Either change
the line in the macro, or copy the macro contents to your appDidFinishLaunching method, and change the line above to simply:

[CCDirector setDirectorType:kCCDirectorTypeNSTimer] ;


Using a view controller and a UIScrollView together

Detecting touches in UIScrollView

One of the necessary steps to getting this to work properly would be to detect both touches and swipes in the UIScrollView instance and pass those messages along to the Cocos window.  Researching this in Google revealed the following forum posting which was a huge help.  The solution there is to use a UIViewController where the view in that controller is an instance of UIScrollView.  Here’s what that setup looks like in our codebase…

CocosOverlayViewController.h

#import 
 
@interface CocosOverlayViewController : UIViewController
{
}
@end

CocosOverlayViewController.m

#import "CocosOverlayViewController.h"
#import "CocosOverlayScrollView.h"
 
@implementation CocosOverlayViewController
 
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
  CocosOverlayScrollView *scrollView = [[CocosOverlayScrollView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
 
  // NOTE - I have hardcoded the size to 1024x1024 as that is the size of the levels in
  // our game.  Ideally this value would be parameterized or configurable.
  //
  scrollView.contentSize = CGSizeMake(1024, 1024);
 
  scrollView.delegate = scrollView;
  [scrollView setUserInteractionEnabled:TRUE];
  [scrollView setScrollEnabled:TRUE];
 
  self.view = scrollView;
 
  [scrollView release];
}
@end

CocosOverlayScrollView.h

#import 
 
@interface CocosOverlayScrollView : UIScrollView
{
}
@end

CocosOverlayScrollView.m

#import "CocosOverlayScrollView.h"
#import "cocos2d.h"
 
@implementation CocosOverlayScrollView
 
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
  if (!self.dragging)
  {
    UITouch* touch = [[touches allObjects] objectAtIndex:0];
    CGPoint location = [touch locationInView: [touch view]];
 
    [self.nextResponder touchesBegan: touches withEvent:event];
    [[[Director sharedDirector] openGLView] touchesBegan:touches withEvent:event];
  }
 
  [super touchesBegan: touches withEvent: event];
}
 
-(void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
  if (!self.dragging)
  {
    [self.nextResponder touchesEnded: touches withEvent:event];
    [[[Director sharedDirector] openGLView] touchesEnded:touches withEvent:event];
  }
 
  [super touchesEnded: touches withEvent: event];
}
 
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
  // TODO - Custom code for handling deceleration of the scroll view
}
 
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
  CGPoint dragPt = [scrollView contentOffset];
 
  Scene* currentScene = [[Director sharedDirector] runningScene];
 
  // Only take the top layer to modify but other layers could be retrieved as well
  //
  Layer* topLayer = (Layer *)[currentScene.children objectAtIndex:0];
 
  dragPt = [[Director sharedDirector] convertCoordinate:dragPt];
 
  dragPt.y = dragPt.y * -1;
  dragPt.x = dragPt.x * -1;
 
  CGPoint newLayerPosition = CGPointMake(dragPt.x + (scrollView.contentSize.height * 0.5f), dragPt.y + (scrollView.contentSize.width * 0.5f));
 
  [topLayer setPosition:newLayerPosition];
}
 
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
  CGPoint dragPt = [scrollView contentOffset];
 
}
@end

This is by no means a perfect solution yet as I still have some values hardcoded such as the size of the content for the scroll view window. I will certainly be working to improve the finer points but hopefully the core issues I’ve put together will be useful to some of you.  Our next dev video (when we get it posted) will show the scroll view in action.

Our Games

Latest Tweets