"'CCLayer' may not respond to ... " - iphone

Playing with cocos2d and this seems to come up in some instances of message passing. I have to admit, I'm not well versed in Objective-C yet as I'm reading through the book as I write some code.
For people familiar with cocos2d, you have a CCLayer class. In my case, I have a GameLayer which has some children layers, like so:
#interface GameLayer : CCLayer {
CCSprite *_background;
CCPanZoomController *_controller;
CCLayer *_truckLayer;
}
I then have a special initWithLayers method to add my child layers within this layer:
-(id)initWithLayers:(TruckLayer *)truck
{
if( (self=[super init])) {
self.truckLayer = truck;
.....
}
Now if I ever send a message to self.truckLayer I get the following warning in Xcode:
'CCLayer' may not respond to 'getNextAvailableSpot'
An example might be doing something like so:
CGPoint nextSpot = [self.truckLayer getNextAvailbleSpot];
All this code compiles and runs successfully, so I'm curious as to what I am missing here? I don't want my code literred with these if I'm doing something wrong here.

It's because your declare *_truckLayer as CCLayer, but on your init you assign (TruckLayer *)truck to your CCLayer* _truckLayer. So CCLayer doesn't know about getNextAvailableSpot method, so the compiler gives you warning.
The code runs fine because _truckLayer is a pointing to correct TruckLayer, which knows about getNextAvailableSpot.
Simply change your declaration and the warning will be gone.
#interface GameLayer : CCLayer
{
CCSprite *_background;
CCPanZoomController *_controller;
CCLayer *_truckLayer;
}
to
#class TruckLayer;
#interface GameLayer : CCLayer
{
CCSprite *_background;
CCPanZoomController *_controller;
TruckLayer *_truckLayer;
}

Just as the warning says: the compiler can't guarantee that CCLayer has a method to handle the getNextAvailableSpot message. Unless you've somehow added in such a method dynamically, or have some other (very good) reason to believe that this method is present, this is probably a problem.

Related

Drawing Waveform with AVAssetReader and with ARC

I'm trying to apply Unsynchronized's answer (Drawing waveform with AVAssetReader) while using ARC. There were only a few modifications required, mostly release statements. Many thanks for a great answer! I'm using Xcode 4.2 targeting iOS5 device.
But I'm getting stuck on one statement at the end while trying to invoke the whole thing.
Method shown here:
-(void) importMediaItem {
MPMediaItem* item = [self mediaItem];
waveFormImage = [[UIImage alloc ] initWithMPMediaItem:item completionBlock:^(UIImage* delayedImagePreparation){
[self displayWaveFormImage];
}];
if (waveFormImage) {
[self displayWaveFormImage];
}
}
On the call to initWithMPMediaItem I get the following error:
Automatic Reference Counting Issue. Receiver type 'UIImage' for instance message
does not declare a method with selector 'initWithMPMediaItem:completionBlock:'
Since I do have the method initWithMPMediaItem declared in the class header, I really don't understand why I'm getting this error.
- (id) initWithMPMediaItem:(MPMediaItem*)item
completionBlock:(void (^)(UIImage* delayedImagePreparation))completionBlock;
Been trying to wrap my head around this for several hours now but to no avail. Is my method declaration wrong for this type of method? Thanks!
It looks like initWithMPMediaItem should be declared as an initializer for UIImage. So you should declare it inside a UIImage category in your header file:
#interface UIImage (MPMedia)
- (id) initWithMPMediaItem:(MPMediaItem*)item
completionBlock:(void (^)(UIImage* delayedImagePreparation))completionBlock;
#end

something wrong with class initing?

I have a class "Hero", subclassed from CCSpriteBatchNode (cocos2d):
#interface Hero : CCSpriteBatchNode {...}
I'm initing it like this:
Hero *hero;
...
hero = [[Hero alloc] initWithWorld:world];
Here is init method of Hero:
-(id) initWithWorld:(b2World*)world
{
if (self = [super init])
{
...
}
return self;
}
Till this moment all works fine: I can add hero as a child to my layer, and it will be displayed correctly. But then, I'm trying to get some data from hero. I have this method in Hero class:
-(b2Body*) getBody; //in Hero.h
-(b2Body*) getBody //in Hero.mm
{
return selfBody;
}
and my application crashes just after calling [hero getBody]; with error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CCSpriteBatchNode getBody]:
unrecognized selector sent to instance 0x42bde0'
so, he tries to find method getBody in CCSpriteBatchNode class. offcorse, he cant find it there. But WHY he tries to find it there? Why not in Hero class?
P.S. all my classes are *.mm. I need it to support box2d.
EDIT
The ony time I use hero between initing and calling getBody is adding it to CCLayer:
[self addChild:hero z:0 tag:kTagParentNode]; //self - CCLayer subclass
getBody is called from tick method:
[self schedule:#selector(tick:)];
...
-(void)tick:(float)dt
{
CGPoint scaledVelocity = ccpMult(leftJoystick.velocity, 480.0f);
if (scaledVelocity.y > CGPointZero.y)
{
[objectControls jump:[hero getBody]];
}
if (scaledVelocity.x > CGPointZero.x)
{
}
}
While it's hard to tell what's causing the issue without seeing much code, I advise you to rethink your architecture.
A CCSpriteBatchNode is an object that's used by the cocos2d engine to render batched sprites. Is your Hero really such an object? I don't think so. The Hero should be a class on it's own and have a has one relation to a CCSpriteBatchNode and a b2Body.
Why? There are several reasons for this. First and foremost it's going to decouple your Hero implementation from the interface imposed by cocos2d. If the creators of cocos2d decide to change some implementation details, your class might no longer work if it inherits from a CCNode. Also think about encapsulation: Does a visual component really have to know about player logic?
If you split up your architecture you're going to make your code better readable, easier to maintain and also easier to port to another platform if that's ever going to happen. For a similar post, see this question on gamedev.SE.

CCSprite is not displaying on the iPhone

I am trying to make a sprite display on the screen in Cocos2d. But, I don't want to use a CCSprite directly. I have a class Unit which will have some additional properties that I will need later on in my game. The class declaration of Unit is as follows:
#interface Unit : CCSprite {
CCSprite *sprite;
}
-(void)init;
#property(nonatomic, retain) NSNumber *type;
#property(nonatomic, retain) CCSprite *sprite;
#end
And my init method for it looks like this:
-(void)init {
self.sprite = [CCSprite spriteWithFile:#"BasicUnit.png"];
self.sprite.position = ccp(50, 100);
}
Now what I need to do is apply it to the screen. So, I have another class called Playscene which is the scene where I want to display sprites and things. Here is what the init method (the method that should draw the sprites) looks like in Playscene:
-(id) init {
if( (self=[super init] )) {
self.isTouchEnabled = YES;
[army init];
[self addChild:army.sprite];
}
return self;
}
But, when I run this I get a ton of error data including: "terminate called after throwing an instance of 'NSException'" and probably of more importance: "Assertion failure in -[PlayScene addChild:]". I don't know how I can solve this. Any help would be appreciated.
Based on your snippets it is very difficult to know what goes wrong. That said I assume that army in your last snipped is of type Unit. But because that is inside the init() method it could be that it is nil because it is not created here like in army = [[Unit] alloc] initXXX];.
That said I am not sure what you want to accomplish with subclassing CCSprite in your Unit class because you are referencing CCSpirit and so there is not need to subclass it.
Finally your Assertion is probably because your army.spirit is either NIL or it is already added and the assertion inside Coco2d throws the exception (I am assuming that the last snippet is from a subclass of CCNode).
My suggestions:
Don't extend CCSpirit in Unit (not needed as far as I can see)
Don't have a method - (void) init but overwrite - (id) init
Make sure army is properly instantiated using [[Unit alloc] init] (see point above)
Using alloc you need to make sure that if it is assigned to a retaining property that you also release it to offset the alloc.
Hope that helps.

Accessing a class' instance variable (NSMutable Array) from another class

new to Obj C and programming in general - learned a lot from this site and really appreciate everyone's contributions.
My scenario is as follows (programming an iPhone game which explains the funny names)
In my main gameLoop (which is in my view controller) if a certain condition is met I create an enemy - the cherry bomb
if (bounceCounterGlobal % 2 == 0 && bounceCounterGlobal > 1 && cherryBombSwitch == 0){
[self addCherryBomb];
}
The addCherryBomb method is as follows:
-(void) addCherryBomb{
CherryBomb *myCherryBomb = [[CherryBomb alloc] init];
[cherryBombArray insertObject:myCherryBomb atIndex:0];
[myCherryBomb release];
[[cherryBombArray objectAtIndex:0] initializeCherryBomb];
[self.view addSubview:[[cherryBombArray objectAtIndex:0] cherryBombView]];
cherryBombSwitch = 1;
}
The CherryBomb header file is short:
#import <Foundation/Foundation.h>
#import "SimpleGameViewController.h"
#interface CherryBomb : NSObject {
UIImageView *cherryBombView;
NSTimer *cherryBombDetonateTimer;
NSTimer *cherryBombMoveTimer;
}
#property (nonatomic, retain) UIView *cherryBombView;
-(void) initializeCherryBomb;
-(void) detonateCherryBomb;
-(void) moveCherryBomb;
#end
What I would like to do is when the cherry bomb detonates (which is determined within the cherryBomb object), I would like the object to remove itself from the cherryBombArray which is an ivar of the view controller.
I tried calling a view controller class method to do this - but I am unable to access ivars of the view controller (because it is a class method). I do not know how to communicate back to the view controller class to tell it to remove the exploded object.
#implementation CherryBomb
...
-(void) detonateCherryBomb{
NSLog(#"KABOOM!");
cherryBombDetonateTimer = nil;
[cherryBombMoveTimer invalidate];
[cherryBombView removeFromSuperview];
//I would like to remove this object from the view controller's cherryBombArray
}
#end
Your help is greatly appreciated. Thank you in advance!
I recommend you to create some "Environment" object that will handle all the gaming logic.
The cherryBomb shouldn't deal with its explosion. The cherryBomb can store many information (size of the explosion, type of explosion, and so on), but the effects of the cherryBomb on the others "things" (characters, bombs, whatever) shouldn't be calculated by the cherryBomb itself.
I'm not very used to game programming, but this aspect of architecture/design is common: each object/class has its responsibilities.
The cherryBomb represents a bomb, no more (and not the "graphic" aspect either).
The Environnement represents a "world" at current instant, and modelizes actions/interactions between the elements of the world.
There is a lot to say about the best way to design a game...
Anyway, to give your question an answer, you can still use "events". The bomb can send a message to your controller telling him: "I've exploded, remove me".
In the bomb:
[[NSNotificationCenter defaultCenter] postNotificationName:#"kaBOOM"
object:self];
In the controller:
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodToCallWhenKaBOOM)
name:#"kaBOOM"
object:nil];
And
- (void)methodToCallWhenKaBOOM:(NSNotification *)note
{
// do stuffs
}
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html
There are a number of ways to do this, and you may want to think about the exact division of labour here, in terms of controllers and models. Things can get pretty spaghettified if you have too much calling back and forth.
However, without getting into all of that, the basic thing you need to do to allow one object to access another object is to give the first a reference to the second.
In this case, you're actually creating the CherryBomb in the view controller, so it's easy to just pass it a reference at that point. Give your CherryBomb class another ivar like this:
SimpleGameViewController* cherryBombViewController;
Modify CherryBomb so that either the init method or your initializeCherryBomb one (these probably should just be a single method, btw) takes such a pointer and assigns it to the ivar:
- (void) initializeCherryBomb:(SimpleGameViewController*)vc
{
// ... whatever other stuff you do in here, plus something like:
cherryBombViewController = vc;
}
When you call this, pass it self as the vc parameter. Then later on, when your bomb detonates it can invoke some method you add on the controller to remove itself:
[cherryBombViewController handleDetonationOfCherryBomb:self];
Note that you absolutely should not access the controller's array directly -- that's an implementation detail your bomb should have no knowledge of. You can get away with being a little sloppy in your control structures in simple cases, but never screw with your encapsulation.
Read up on the MVC design pattern. If you find ivars which you need to share among views, they should probably go in a higher level Model object (the M of MVC), instead of having some view's peaking into other view's ivars. A pointer to this Model object can then be passed down to all the view objects that need to access it.
I think this is a good application of key value observing. You need a property of the cherry bomb which represents its state e.g.
#property (assign) BOOL isExploded;
Any object that is interested in whether the cherry bomb has exploded registers itself for KVO on the isExploded property. For example the view controller might do:
[cherryBomb addObserver: self
forKeyPath: #"isExploded"
options: ....
context: ....];
and in -observeValueForKeyPath:ofObject:change:context: for the view controller remove the cherry bomb from the array.
Your detonate method does the following as well as everything else it is currently doing:
[self setExploded: YES];

Is there a way to retrieve every responder that has handled a UITouch?

I'm trying to debug some touchesBegan/Moved/Ended related slowdown in my game; I think that some of my touch responders are not unloading properly, and so as the game runs on more and more of them stack up and the touches get less responsive because they have to pass through a larger and larger responder chain.
Is there some way to view/retrieve the path taken by a UITouch as it moves through the chain? Or simply some way to retrieve a list of all active responders?
Thanks,
-S
You can hijack the desired methods on UIResponder to add logging and then call the original method. Here's an example:
#import <objc/runtime.h>
#interface UIResponder (MYHijack)
+ (void)hijack;
#end
#implementation UIResponder (MYHijack)
+ (void)hijackSelector:(SEL)originalSelector withSelector:(SEL)newSelector
{
Class class = [UIResponder class];
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method categoryMethod = class_getInstanceMethod(class, newSelector);
method_exchangeImplementations(originalMethod, categoryMethod);
}
+ (void)hijack
{
[self hijackSelector:#selector(touchesBegan:withEvent:) withSelector:#selector(MYHijack_touchesBegan:withEvent:)];
}
- (void)MYHijack_touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touches!");
[self MYHijack_touchesBegan:touches withEvent:event]; // Calls the original version of this method
}
#end
Then somewhere in your app (I sometimes put it in main() itself), just call [UIResponder hijack]. As long as the UIResponder subclass calls super at some point, your code will be injected.
method_exchangeImplementations() is a beautiful thing. Be careful with it of course; it's great for debugging, but very confusing if used indiscriminately.
I would look into your memory usage, rather than trying to retrieve all the responders.
Either look into instruments http://www.mobileorchard.com/find-iphone-memory-leaks-a-leaks-tool-tutorial/
or conversely, just throw in a few NSLog(#"responded"); into your touchesBegan methods and see if it gets logged like ten times for each touch.