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.