Tiny Defense: Using UIScrollView in Cocos2d by Sash

So as you know that I spent the weekend trying to get xcode, objective c and cocos2d to be my bitch.

Most of my time I was working on a solution to implementing scrollviews in cocos2d. Turns out this isn’t as straight forward as I thought, but I found a tutorial over at getsetgames.com which gave me a good place to start. I have to warn you, the code in that tutorial is pretty messy. I’ve cleaned it up a lot below.

The Problem

Cocos 2d has no equivalent to UIScrollView. You can’t even use the regular UIScrollview, since it doesn’t fit into the framework. This leaves you with two possible solutions:

  1. Reverse engineer the UIScroll View
    This would take a long time to get right. Apple has put a lot of polish into making the touch interactions perfect. There are a ton of edge cases and the code is not available to copy. Ouch.
  2. Implement an invisible UIScrollView
    And use its contentOffset property to position your CCNode. Much better!

The later solution is much cleaner and has a straight forward implementation. Basically it works like this. We subclass UIScrollView, make sure it is invisible, put it on top of Cocos2d so that the user is interacting with it, then take the scroll position and apply it to the stuff in cocos2D each frame. There are a few challenges that we will have a look at, but check out the code I wrote first:

Code: CCScrollView.h

#import "cocos2d.h"

@interface CCScrollView : UIScrollView

+ (CCScrollView *) makeScrollViewWithWidth:(int)width Height:(int)height;

- (void)setWidth:(int)width Height:(int)height;
- (int) getOffset;
- (CGPoint) getOffsetAsPoint;

@end

Code: CCScrollView.m

#import "CCScrollView.h"

@implementation CCScrollView

/*
* Returns a CCScrollView Object.
* You can treat this like a regular UIScrollView, but don't add subviews. Instead
* use the methods getOffset and getOffsetAsPoint to set the Y Position of the
* CCNode you want to scroll each frame
*/
+(CCScrollView *) makeScrollViewWithWidth:(int)width Height:(int)height
{
   CCScrollView * scrollView =
     [[CCScrollView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
   scrollView.contentSize = CGSizeMake(width, height);
   scrollView.showsHorizontalScrollIndicator = NO;
   scrollView.showsVerticalScrollIndicator = NO;
   [scrollView setUserInteractionEnabled:TRUE];
   [scrollView setScrollEnabled:TRUE];

   [[[CCDirector sharedDirector] openGLView] addSubview:scrollView];

   return scrollView;
}

/*
* Change the size of the scrollable area
*/
- (void)setWidth:(int)width Height:(int)height
{
   self.contentSize = CGSizeMake(width, height);
}

/*
* Returns the offset of the scrollview as a point
*/
- (CGPoint) getOffsetAsPoint
{
   CGPoint offset = [self contentOffset];
   offset = [[CCDirector sharedDirector] convertToGL: offset];
   offset.y *= -1;
   offset.x *= -1;

   return offset;
}

/*
* Returns the Y co-ordinate of the offset of the ScrollView
*/
- (int) getOffset
{
   CGPoint offset = [self getOffsetAsPoint];
   return offset.y;
}

- (void) dealloc
{
   [self removeFromSuperview];
   [super dealloc];
}

/*
* Override touch functions
* This allows Cocos2d to process touches
*/
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
   if (!self.dragging)
      [[[CCDirector sharedDirector] openGLView] touchesBegan:touches withEvent:event];

   [super touchesBegan: touches withEvent: event];
}

-(void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
   if (!self.dragging)
      [[[CCDirector sharedDirector] openGLView] touchesEnded:touches withEvent:event];

   [super touchesEnded: touches withEvent: event];
}
@end

Code: Implementation.m

//
//  Implementation.m
//  TinyDefense
//
//  Created by Sasha MacKinnon on 21/09/11.
//  Copyright 2011 Bit Battalion. All rights reserved.
//

#import "Implementation.h"

@implementation Implementation

+ (CCScene *) scene
{
    CCScene * s = [CCScene node];
    Implementation * l = [Implementation node];
    [s addChild:l];

    return s;
}

- (id)init
{
    if (self = [super init])
    {
        gameNode = [[CCNode alloc] init];

        //add the background to the game
        background = [CCSprite spriteWithFile:@"background.png"];
        background.position = ccp(160, 480);
        [gameNode addChild:background];
        [self addChild: gameNode];

        //set up scrolling
        scrollView = [CCScrollView makeScrollViewWithWidth:320 Height:960];

        [self schedule:@selector(enterFrame:)];
    }

    return self;
}

- (void) enterFrame: (ccTime) dt
{
    gameNode.position = ccp(0, [scrollView getOffset]);
}
@end

Setting up the UIScrollView

We want an easy way to create scroll views without having to go through the hassle of manually allocing and setting properties every time w need a new one. The makeScrollViewWithWidth method does just this. It:

  • creates the scroll view,
  • sets its content size to the arguments
  • adds itself to the OpenGLView, so it appears on the screen
+(CCScrollView *) makeScrollViewWithWidth:(int)width Height:(int)height
{
    CCScrollView * scrollView =
       [[CCScrollView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
    scrollView.contentSize = CGSizeMake(width, height);
    scrollView.showsHorizontalScrollIndicator = NO;
    scrollView.showsVerticalScrollIndicator = NO;
    [scrollView setUserInteractionEnabled:TRUE];
    [scrollView setScrollEnabled:TRUE];

    [[[CCDirector sharedDirector] openGLView] addSubview:scrollView];

    return scrollView;
}

The contentOffset Property

Next we need is to be able to access the position of our ScrollView after the user has scrolled. Since CCScrollView is a subclass of UIScrollView, we could access the “contentOffset” property directly, but we need to do a few transformations on that before hand to make it work the way we want. I made getter methods to make life easier:

- (CGPoint) getOffsetAsPoint
{
    CGPoint offset = [self contentOffset];
    offset = [[CCDirector sharedDirector] convertToGL: offset];
    offset.y *= -1;
    offset.x *= -1;

    return offset;
}

- (int) getOffset
{
    CGPoint offset = [self getOffsetAsPoint];
    return offset.y;
}

Making sure touches are registered by cocos2D

Right now we can have elements scrolling on the screen, but we can’t touch any of them. This is because CCScrollView is sitting on top of cocos2d, so the touches stop being processed before they hit cocos2D. To solve this we override touchBegan and touchEnd, and pass the touches down to cocos2D

-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
    if (!self.dragging)
        [[[CCDirector sharedDirector] openGLView] touchesBegan:touches withEvent:event];
    [super touchesBegan: touches withEvent: event];
}

-(void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
    if (!self.dragging)
        [[[CCDirector sharedDirector] openGLView] touchesEnded:touches withEvent:event];
    [super touchesEnded: touches withEvent: event];
}

Fixing the content offset bug

But it STILL won’t work. Cocos2D, for some weird reason, isn’t prepared to deal with the touches we get from UIScrollView. It offsets the touches by a small margin when you scroll down far enough. I found a great solution online, which requires a quick change to CCNode.m

http://stackoverflow.com/questions/2457380/uiscrollview-and-cocos2d

Basically replace these functions in CCNode.m:

- (CGPoint)convertTouchToNodeSpace:(UITouch *)touch {
    // cocos2d 1.0 rc bug when using with additional overlayed views (such as UIScrollView).
    // CGPoint point = [touch locationInView: [touch view]];
    CGPoint point = [touch locationInView: [[CCDirector sharedDirector] openGLView]];
    point = [[CCDirector sharedDirector] convertToGL: point];
    return [self convertToNodeSpace:point];
}

- (CGPoint)convertTouchToNodeSpaceAR:(UITouch *)touch {
    // cocos2d 1.0 rc bug when using with additional overlayed views (such as UIScrollView).
    // CGPoint point = [touch locationInView: [touch view]];
    CGPoint point = [touch locationInView: [[CCDirector sharedDirector] openGLView]];
    point = [[CCDirector sharedDirector] convertToGL: point];
    return [self convertToNodeSpaceAR:point];
}

and CCMenu.m

-(CCMenuItem *) itemForTouch: (UITouch *) touch {
    // cocos2d 1.0 rc bug when using with additional overlayed views (such as UIScrollView).
    // CGPoint touchLocation = [touch locationInView: [touch view]];
    CGPoint touchLocation = [touch locationInView: [[CCDirector sharedDirector] openGLView]];
    touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
    ...

And now it’s ready to go!! The code is here in full, so feel free to copy paste these classes.

Fixing it for iOS 5

After all this, it might still not work with iOS 5. Instead of explaining why, I’ll point you to a stack overflow answer that has the solution:

Animation in OpenGL ES view freezes when UIScrollView is dragged on the iPhone

Whats Next

I’ve done a ton of work since I made this video which I’ll post in a few days. Tonight, I’m travelling back to Australia! I’m hoping to get a bit of code written on the plane before my battery dies. Wish me luck!


Discussion

  1. You need to be a part of a contest for one of the greatest websites on the net. I will recommend this blog!

  2. Hollie says:

    Wait, I cannot fathom it being so stagaihtforwrrd.

  3. Useful info. Fortunate me I found your website unintentionally, and I’m surprised why this accident did not came about in advance! I bookmarked it.

  4. radins says:

    That is a really good tip particularly to those fresh to the blogosphere. Short but very precise info… Thank you for sharing this one. A must read article!

  5. Hello Good Day , I will come the blog for try to find an stimulus or else an fascinating blog. Serious blog, be grateful for sharing. Fabien

  6. My brother recommended I might like this web site. He was totally right. This post actually made my day. You can not imagine just how much time I had spent for this information! Thanks!

  7. I think this is one of the most vital info for me. And i am glad reading your article. But should remark on some general things, The website style is ideal, the articles is really excellent : D. Good job, cheers

  8. Nice blog here! Also your web site loads up fast! What host are you using? Can I get your affiliate link to your host? I wish my website loaded up as quickly as yours lol

  9. ppob tektaya says:

    whoah this weblog is great i really like reading your articles. Stay up the good work! You realize, a lot of individuals are hunting around for this info, you could help them greatly.

  10. I have recently started a blog, the information you provide on this website has helped me greatly. Thanks for all of your time & work.

  11. ppob says:

    I keep listening to the news speak about receiving boundless online grant applications so I have been looking around for the most excellent site to get one. Could you tell me please, where could i acquire some?

  12. I really like it whenever people get together and share thoughts. Great website, stick with it!

  13. It’s really a nice and useful piece of information. I am glad that you shared this helpful information with us. Please keep us up to date like this. Thank you for sharing.

  14. You have noted very interesting points! ps decent internet site.

  15. It?¦s really a great and useful piece of information. I am glad that you simply shared this helpful information with us. Please keep us informed like this. Thanks for sharing.

  16. Home Accents says:

    Valuable info. Lucky me I found your site by accident, and I am stunned why this accident didn’t came about earlier! I bookmarked it.

  17. ppob arindo says:

    fantastic issues altogether, you simply gained a logo new reader. What might you suggest in regards to your submit that you just made a few days in the past? Any sure?

  18. Great post. I was checking constantly this blog and I’m impressed! Extremely helpful information specially the last part :) I care for such information much. I was looking for this certain info for a very long time. Thank you and good luck.

  1. URL linked here

    [... So as you know that I spent the weekend trying to get xcode, objective c and cocos2d to be my bitch. Most of my time I was working on a solution ...]

  2. Google linked here

    [... So as you know that I spent the weekend trying to get xcode, objective c and cocos2d to be my bitch. Most of my time I was working on a solution ...]

  3. Google linked here

    [... So as you know that I spent the weekend trying to get xcode, objective c and cocos2d to be my bitch. Most of my time I was working on a solution ...]

Leave a Comment