CCUIViewWrapper – wrapper for manipulating UIViews using Cocos2D

Forums Programming cocos2d 3rd party extensions CCUIViewWrapper – wrapper for manipulating UIViews using Cocos2D

This topic contains 83 replies, has 65 voices, and was last updated by  J-Crew 2 months, 2 weeks ago.

Viewing 25 posts - 1 through 25 (of 84 total)
Author Posts
Author Posts
June 6, 2010 at 4:24 pm #222227

Blue Ether
Participant
@blue-ether

I wanted to share a class I’ve written I’m calling CCUIWrapper, which acts as a way of manipulating UIViews via Cocos2D methods. It’s not the most momumental class ever written and most of what it does is simple but a few of the pieces took a bit of tinkering and head scratching to put together. I’m sharing this because I think many people might find it very useful, and I’m inclined to believe that it should even be included as part of Cocos2D. UIViews can do many things that Cocos cannot, and the differences between the two systems make them very confusing to merge together. Hopefully this class simplifies the process for people.

Applications:

I originally started developing this to be used for fading and moving AdMob ads. I have continued tinkering with it for use with an iPad app that has a few UITextFields and where rotations are needed to allow for orientation changes.

First, how to initialize a UIView with the wrapper:

UIView *myView	= [[[UIView alloc] init] autorelease];
CCUIViewWrapper *wrapper = [CCUIViewWrapper wrapperForUIView:myView];
wrapper.contentSize = CGSizeMake(320, 160);
wrapper.position = ccp(64,64);
[self addChild:wrapper];

// cleanup
[self removeChild:wrapper cleanup:true];
wrapper = nil;

The wrapper puts a retain on its UIView, so you can autorelease it as you create it. In practice though, you probably want to maintain a seperate reference to the UIView since you don’t want to have to keep casting and type checking the UIView to see if its actually the UITextField (or whatever else) that you created.

The view doesn’t have to be initialized with a frame size (though it can be), because setting the content size of the wrapper automatically resizes the frame. Also, adding the wrapper to another Cocos object will automatically add its associated uiView to the Cocos openGLView window.

Next, usage:

[wrapper runAction:[CCRotateTo actionWithDuration:.25f angle:180]];
wrapper.position = ccp(96,128);
wrapper.opacity = 127;
[wrapper runAction:[CCScaleBy actionWithDuration:.5f scale:.5f];
wrapper.visible = false;

After creation, you can basically treat the wrapper as if it were the UIView. Anywhere you position the wrapper, that’s where the UIView will be. Similarly, you can modify visibility, opacity, rotation, and scale. The UIView is automatically updated. This includes modifications made via actions.

Questions / Limitations / Missing functionalities:

- I’ve tried to make the wrapper include transforms of its parents. However, I’m not sure if this includes scale or opacity, as I haven’t tested it. Honoring parent rotations was a bit of work to get set up, and I didn’t need to worry about honoring parent scale or parent opacity for my application.

- I’m not sure if rotations will work right unless the anchor point of the wrapper is at .5,.5 (though position should work fine). I was only using center anchor points, so it didn’t come up.

- I’m also not sure if you add subviews to your UIView whether those will be positioned properly, though I assume they will

- If you transform the wrapper’s parent/grandparent/etc, the wrapper can handle that, but it needs to be told it has to recalculate the uiView’s position. You can do this by calling: [wrapper updateUIViewTransform];

If, for example, you are running a CCRotateTo action on the wrapper’s parent, then updateUIViewTransform will need to be called every frame during that action. Maybe there is a better way of integrating the update method so that Cocos will automatically trigger the code? I’m not very familiar with the very low level of how Cocos lets children know their parent has changed transformation.

Finally, here is the source:

CCUIViewWrapper.h

#import "cocos2d.h"

@interface CCUIViewWrapper : CCSprite
{
UIView *uiItem;
float rotation;
}

@property (nonatomic, retain) UIView *uiItem;

+ (id) wrapperForUIView:(UIView*)ui;
- (id) initForUIView:(UIView*)ui;

- (void) updateUIViewTransform;

@end

CCUIViewWrapper.m

#import "CCUIViewWrapper.h"

@implementation CCUIViewWrapper

@synthesize uiItem;

+ (id) wrapperForUIView:(UIView*)ui
{
return [[[self alloc] initForUIView:ui] autorelease];
}

- (id) initForUIView:(UIView*)ui
{
if((self = [self init]))
{
self.uiItem = ui;
return self;
}
return nil;
}

- (void) dealloc
{
self.uiItem = nil;
[super dealloc];
}

- (void) setParent:(CCNode *)parent
{
if(parent == nil)
[uiItem removeFromSuperview];
else if(uiItem != nil)
[[[[CCDirector sharedDirector] openGLView] window] addSubview: uiItem];
[super setParent:parent];
}

- (void) updateUIViewTransform
{
float thisAngle, pAngle;
CGAffineTransform transform = CGAffineTransformMakeTranslation(0, [[UIScreen mainScreen] bounds].size.height);

for(CCNode *p = self; p != nil; p = p.parent)
{
thisAngle = CC_DEGREES_TO_RADIANS(p.rotation);

if(!p.isRelativeAnchorPoint)
transform = CGAffineTransformTranslate(transform, p.anchorPointInPixels.x, p.anchorPointInPixels.y);

if(p.parent != nil)
{
pAngle = CC_DEGREES_TO_RADIANS(p.parent.rotation);

transform = CGAffineTransformTranslate(transform,
(p.position.x * cosf(pAngle)) + (p.position.y * sinf(pAngle)),
(-p.position.y * cosf(pAngle)) + (p.position.x * sinf(pAngle)));
}
else
transform = CGAffineTransformTranslate(transform, p.position.x, -p.position.y);

transform = CGAffineTransformRotate(transform, thisAngle);
transform = CGAffineTransformScale(transform, p.scaleX, p.scaleY);

transform = CGAffineTransformTranslate(transform, -p.anchorPointInPixels.x, -p.anchorPointInPixels.y);
}

[uiItem setTransform:transform];
}

- (void) setVisible:(BOOL)v
{
[super setVisible:v];
[uiItem setHidden:!v];
}

- (void) setRotation:(float)protation
{
[super setRotation:protation];
[self updateUIViewTransform];
}

- (void) setScaleX:(float)sx
{
[super setScaleX:sx];
[self updateUIViewTransform];
}

- (void) setScaleY:(float)sy
{
[super setScaleY:sy];
[self updateUIViewTransform];
}

- (void) setOpacity:(GLubyte)opacity
{
[uiItem setAlpha:opacity/255.0f];
[super setOpacity:opacity];
}

- (void) setContentSize:(CGSize)size
{
[super setContentSize:size];
uiItem.frame = CGRectMake(0, 0, self.contentSize.width, self.contentSize.height);
uiItem.bounds = CGRectMake(0, 0, self.contentSize.width, self.contentSize.height);
}

- (void) setAnchorPoint:(CGPoint)pnt
{
[super setAnchorPoint:pnt];
[self updateUIViewTransform];
}

- (void) setPosition:(CGPoint)pnt
{
[super setPosition:pnt];
[self updateUIViewTransform];
}

@end

EDIT: I should note, I developed this for 0.99.2. I haven’t tested it with 0.99.3.

June 6, 2010 at 5:43 pm #285620

liquidfire
Participant
@liquidfire

Looks very useful. Thanks!

June 6, 2010 at 9:25 pm #285621

brendang
Participant
@brendang

Hey, terrific code. Thanks, Pal!

June 8, 2010 at 3:46 pm #285622

Dunc
Participant
@dunc

Wow, nice work dude! Thanks for this. I love this community.

D.

July 10, 2010 at 10:16 am #285623

asinesio
Participant
@asinesio

This is fantastic.

Thank you!!!

July 29, 2010 at 2:31 pm #285624

popgoblin
Participant
@popgoblin

did anyone figure out how to let this wrapper honor parent transforms?

July 29, 2010 at 2:52 pm #285625

popgoblin
Participant
@popgoblin

sry…. got that myself:

overwrite:

-(void) transform

{

[wrapper updateUIViewTransform];

[super transform];

}

August 5, 2010 at 1:09 pm #285626

Sat Back
@sat-back

@Blue Ether

Thanks, this looks great! I want try it with iAd.

Do you want credit given for the class? The regular library files have headers like:

/* cocos2d for iPhone

*

* http://www.cocos2d-iphone.org

*

* Copyright (C) 2008,2009 Ricardo Quesada

* Copyright (C) 2009 Valentin Milea

*

* This program is free software; you can redistribute it and/or modify

* it under the terms of the ‘cocos2d for iPhone’ license.

*

* You will find a copy of this license within the cocos2d for iPhone

* distribution inside the “LICENSE” file.

*

*/

Regards

August 12, 2010 at 2:27 pm #285627

bobueland
Participant
@bobueland

If there are any newbies (like myself) out there that need extra help how to integrate a UIKit item with cocos2d, this might help. As you have read in the first post of this thread Blue Ether has written a wrapper for manipulating UIViews. What is a UIView? Look at http://developer.apple.com/iphone/library/documentation/uikit/reference/uikit_framework/Introduction/Introduction.html, scroll down a little bit and you will see a diagram of different UIView items; things like UIButton, UISlider, UITextView, UILabel, UIAlertView, UITableView, UIWindow and so on. There are more then 20 of them. You can use Blue Ethas code to wrap any of them inside cocos2d. Here I will wrap a UIButton, but experiment with your favorite choice of UIView item.

1. Create a new cocos 2d Application project.

2. Make a new NSObject file and call it CCUIViewWrapper. That will give you the files CCUIViewWrapper.h and CCUIViewWrapper.m. Edit (by copy paste) them so they look as Blue Ether has defined them (see the first post in this thread.

3. Make your HelloWorld.h look like this

//  HelloWorldLayer.h
#import "cocos2d.h"
#import <UIKit/UIKit.h>
#import "CCUIViewWrapper.h"

@interface HelloWorld : CCLayer
{
UIButton *button;
CCUIViewWrapper *wrapper;
}
+(id) scene;
@end

and your HelloWorld.m like this

//  HelloWorldLayer.m
#import "HelloWorldScene.h"

// HelloWorld implementation
@implementation HelloWorld

+(id) scene
{
CCScene *scene = [CCScene node];
HelloWorld *layer = [HelloWorld node];
[scene addChild: layer];
return scene;
}

-(void)buttonTapped:(id)sender
{
NSLog(@"buttonTapped");
}

// create and initialize a UIView item with the wrapper
-(void)addUIViewItem
{
// create item programatically
button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchDown];
[button setTitle:@"Touch Me" forState:UIControlStateNormal];
button.frame = CGRectMake(0.0, 0.0, 120.0, 40.0);

// put a wrappar around it
wrapper = [CCUIViewWrapper wrapperForUIView:button];
[self addChild:wrapper];
}

-(id) init
{
if( (self=[super init] )) {
[self addUIViewItem];

// x=160=100+120/2, y=240=260-40/2
wrapper.position = ccp(100,260);
[wrapper runAction:[CCRotateTo actionWithDuration:4.5 angle:180]];
}
return self;
}

- (void) dealloc
{
[self removeChild:wrapper cleanup:true];
wrapper = nil;
[button release];
button=nil;
[super dealloc];
}
@end

Build and Run. This should produce a rotating button in the middle of the scene. You can touch the button while it rotates. It will write a text message in the Console.

August 12, 2010 at 5:03 pm #285628

Pedagogic368
@pedagogic368

Would a wrapper be helpful moving from a “scene” to a UIView? Thanks

August 19, 2010 at 9:42 am #285629

GustavPicora
@gustavpicora

Hello.

First of all Great code.. .it helps a lot.

Second I have a little problem, im kind anew here so please dont be so harsh on me :P, when I rotate the device the CCUIViewWrapper doesn’t change position, what should I do? Should I rotate the CCUIViewWrapper when DeviceOrientation notification its triggered?

should I do this on the scene or should I do it inside the CCUIViewWrapper Class?

thx for great code

Gustavo

August 21, 2010 at 8:47 pm #285630

asinesio
Participant
@asinesio

@GustavPicora — I’m seeing the same thing.

I’m wondering if we have to set the UIViewController of the view to disable autorotation. If anyone has thoughts here, please let me know.

August 27, 2010 at 5:46 pm #285631

bens
Participant
@bens

I got autoroation to work (using the kGameAutorotationUIViewController mode in the 0.99.5 templates) by simply adding the uiItem directly to the glView instead of to the window. The root view controller will then handle the subview rotation automagically. I don’t know if this plays well with the CCNode rotation (it should), but it works for a simple fullscreen UIView.

So my setParent method looks like:

- (void) setParent:(CCNode *)parent {
if(parent == nil)
[uiItem removeFromSuperview];
else if(uiItem != nil)
[[[CCDirector sharedDirector] openGLView] addSubview: uiItem];
[super setParent:parent];
}

September 3, 2010 at 9:05 am #285632

hardemr
@hardemr

Firstly, your class is absolutely great. Thank you for your class…

Now, i have a question:

I created UIView on the top of Cocos2D Layer with the help of your class. I want to open this UIView with the animation… How can i do that?

October 8, 2010 at 7:05 am #285633

finder39
Participant
@finder39

I am curious, does this work across multiple scene changes?

i want to have an iad loaded so that at the end of a level it shows. but i dont want to have to load it each time so i figure if i just load it “above” the scene i can use replace scene and just hide it after, then show it again at end of a level

October 8, 2010 at 7:21 pm #285634

wasabibit
Participant
@wasabibit

Thanks for sharing the great code!

This works well with UIView items.

It’s understood that MFMailComposeViewController is not UIView.

With that said, is there a way to make this wrapper work with MFMailComposeViewController by any chance?

Yes, as you guess, I use MFMailComposeViewController for sending InApp email.

October 9, 2010 at 12:12 am #285635

finder39
Participant
@finder39

bump

October 9, 2010 at 1:16 am #285636

wasabibit
Participant
@wasabibit

@finder39,

Trying to figure out what you want to do here – ‘i just load it “above” the scene i can use replace scene and just hide it after, then show it again at end of a level’.

Two things:

1) The CCNode type instance containing the iAd should be kept somewhere in singleton so that it’s not dealloc’ed when iAd layer is ‘replace scene’ed.

2) I have a bit of performance concern after looking at Apple’s comment:

So… if your game requires high and constant FPS, it’s possible that it might drop when your iAd delegate is called during game play even though it’s hidden. I haven’t experimented this though. Some experts might share their experience or shed a light on a solution.

– from Apple doc: http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/iAd_Guide/WorkingwithBannerViews/WorkingwithBannerViews.html

When a banner view has a new advertisement to display, it calls the delegate’s bannerViewDidLoadAd: method. This method is called even if the banner view is not currently part of the view hierarchy. Your application can use this method to attach the view to a view hierarchy or to move the banner view on screen.

November 2, 2010 at 2:21 am #285637

ashmira
Participant
@ashlovemira

nice and thx for sharing it. I want to ask about the run action for CCUIViewWrapper. other than CCRotateTo. any others action can be use to it such as CCFadeIn or CCFadeOut? when i tried it it crash?

December 15, 2010 at 1:18 pm #285638

kliucn
@kliucn

COOL! Thank you so much, it is very useful.

December 22, 2010 at 12:34 pm #285639

RevoltingPeasant
@revoltingpeasant

Hey guys

This is absolutely lovely…but I need to add about 30 UISliders/Switches, etc. to my interface and I’m wondering if I can add multiple views to my wrapper. This dowsn’t work:

myWrapper = [CCUIViewWrapper wrapperForUIView:mySlider, mySlider2, nil];

Is there a way to do this or do I need to make a new wrapper for every item I want to add to my UI.

I made the sliders like this (called “mySlider, mySlider2, etc.”)

-(void)addUIViewItem{
mySlider = [[UISlider alloc]init];
[mySlider addTarget:self action:@selector(mySliderChange) forControlEvents:UIControlEventValueChanged];
[mySlider setBackgroundColor:[UIColor clearColor]];
mySlider.minimumValue = 0.0;
mySlider.maximumValue = 360.0;
mySlider.continuous = YES;
mySlider.value = 25.0;
mySlider.frame = CGRectMake(0.0, 0.0, 100, 100.0);
}

Any tip would be greatly appreciated!

Thank you and cheers

RP

December 28, 2010 at 10:23 pm #285640

crashcar
Participant
@crashcar

@RevoltingPeasant

I believe one wrapper is for one UIItem.

December 29, 2010 at 3:12 am #285641

ashmira
Participant
@ashlovemira

why dun you added the UISlider into 1 UIScrollView and added it into your wrapper. Here is my example i use.

jumpScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(170,90, 310.0f, 230.0f)];
for(int x=0;x<5;x++){
NSDictionary *temp=[((qalviniusAppDelegate *)UIApplication sharedApplication] delegate]).allItems objectForKey:[cartItem objectAtIndex:x;
NSLog(@"%@",temp);
UIImageView *backgroundImage = UIImageView alloc] initWithImage:[UIImage imageNamed:@"boxInApp.png";
[backgroundImage setFrame:CGRectMake(0, 0+(x*77), 299.0f, 71.0f)];
[backgroundImage setBackgroundColor:[UIColor clearColor]];
UIImageView *iconImage = UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat:@"%@",[temp objectForKey:@"image"]];
[iconImage setFrame:CGRectMake(15,15+(x*77), 37.0f, 37.0f)];
[iconImage setBackgroundColor:[UIColor clearColor]];
UIImage *thumbnail = [UIImage imageNamed:[NSString stringWithFormat:@"buyInApp.png"]];
UILabel *weaponStat = [[UILabel alloc] initWithFrame:CGRectMake(55, 6+x*77, 180.0f, 71.0f)];
if ([[temp objectForKey:@"insertable"] isEqual:@"NO"]) {
weaponStat.text = [NSString stringWithFormat:@"Health : %d, Energy: %d",temp objectForKey:@"statHP"] intValue],[[temp objectForKey:@"statMP"] intValue;
}else {
weaponStat.text = [NSString stringWithFormat:@"Phy Dmg: %d - %d t Mgc Dmg: %d - %dnHP: %d MP: %d Str: %d tInt: %d tVit: %d nDex: %d tAgi: %d Evd: %d Def: %d Acc: %d",temp objectForKey:@"Ldamage"] intValue],[[temp objectForKey:@"damage"] intValue],[[temp objectForKey:@"lowerMagicDamage"] intValue],[[temp objectForKey:@"highMagicDamage"] intValue],[[temp objectForKey:@"statHP"] intValue],[[temp objectForKey:@"statMP"] intValue],[[temp objectForKey:@"statStr"] intValue],[[temp objectForKey:@"statInt"] intValue],[[temp objectForKey:@"vit"] intValue],[[temp objectForKey:@"dex"] intValue],[[temp objectForKey:@"agi"] intValue],[[temp objectForKey:@"dodge"] intValue],[[temp objectForKey:@"def"] intValue],[[temp objectForKey:@"accuracy"] intValue;
}

weaponStat.textAlignment = UITextAlignmentLeft;
weaponStat.numberOfLines=5;
[weaponStat setFont:[UIFont fontWithName:@"Arial" size:9.0]];
weaponStat.backgroundColor=[UIColor clearColor];

UILabel *name = [[UILabel alloc] initWithFrame:CGRectMake(55, 4+x*77, 110.0f, 21.0f)];
name.text = [NSString stringWithFormat:@"%@",[temp objectForKey:@"description"]];
name.textAlignment = UITextAlignmentLeft;
[name setFont:[UIFont fontWithName:@"Arial" size:11]];
name.backgroundColor=[UIColor clearColor];
[name setShadowOffset:CGSizeMake(1, 1)];
name.font = [UIFont boldSystemFontOfSize:11];

UILabel *price = [[UILabel alloc] initWithFrame:CGRectMake(150, 4+x*77, 80.0f, 21.0f)];
price.text = [NSString stringWithFormat:@"%d Qalz",temp objectForKey:@"sell"] intValue;
price.textAlignment = UITextAlignmentRight;
[price setFont:[UIFont fontWithName:@"Arial" size:11]];
price.backgroundColor=[UIColor clearColor];
[price setShadowOffset:CGSizeMake(1, 1)];
price.font = [UIFont boldSystemFontOfSize:11];

//[self addText:[UIImage imageNamed:[NSString stringWithFormat:@"helmet%d.png",x+1]] text:[NSString stringWithFormat:@"%d",x+1]];
//UIButton *thumbs = [[UIButton alloc] initWithFrame:CGRectMake(20,20+(x*93), 440.0f, 83.0f)];
UIButton *thumbs = [[UIButton alloc] initWithFrame:CGRectMake(230,3+(x*77), 65.0f, 65.0f)];
[thumbs setImage:thumbnail forState:UIControlStateNormal];
//[thumbs setBackgroundImage:[UIImage imageNamed:@"terLogo2.png"] forState:UIControlStateNormal];
[thumbs setTag:x];
[thumbs addTarget:self action:@selector(BuyTheItem:) forControlEvents:UIControlEventTouchUpInside];
[thumbs setBackgroundColor:[UIColor clearColor]];
//[jumpScroll setBackgroundColor:[UIColor blackColor]];
[jumpScroll addSubview:backgroundImage];
[jumpScroll addSubview:iconImage];
[jumpScroll addSubview:thumbs];
[jumpScroll addSubview:weaponStat];
[jumpScroll addSubview:price];
[jumpScroll addSubview:name];

[price release];
[weaponStat release];
[backgroundImage release];
[iconImage release];
[name release];
[thumbs release];
}
//jumpScroll.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
//[jumpBar addSubview:jumpScroll];
//[jumpScroll setPosition:ccp(150,240)];
[jumpScroll setContentSize:CGSizeMake(300,5*77)];
//[[[CCDirector sharedDirector] openGLView] addSubview: jumpScroll];
wrapper = [CCUIViewWrapper wrapperForUIView:jumpScroll];
wrapper.position = ccp(-165,378);
[wrapper setRotation:90];
[self addChild:wrapper];

December 30, 2010 at 11:55 pm #285642

petem
@petem

This thread is brilliant, thank you. Seeing that the UIView is wrapped by the Layer class, I’ve found that UIView could also be the view of a UIViewController. If the view controller is init’ed via a NIB then the full facilities of the Interface Builder can be used to build up the interface. Make the background a clearColor if you wish to overlay your scene beneath.

Excellent. Thanks again.

December 31, 2010 at 7:43 am #285643

RevoltingPeasant
@revoltingpeasant

hey ashmira!

thank you so much – I feel pretty stupid now:) That’s exactly what I needed to hear!

petem: I think I get what you’re saying, but if you’re working on something along those lines, could you maybe post the code here too? I’ve seen the question on how to do that all over the web, so i’m sure you’d be helping tons of people. I’d rather create my UI programmatically, but I know many people love IB (and so do I, for certain things, just not my current project;).

Thanks!

Viewing 25 posts - 1 through 25 (of 84 total)

You must be logged in to reply to this topic.