Xcode Cocos 2D How To Use Selector With Multiple Parameters - objective-c

I am trying to do something like this:
CCMenuItemImage *BuyButton = [CCMenuItemImage itemWithNormalImage:#"Buy.jpg" selectedImage:#"Buy.jpg" target:self selector:#selector(Function:cnt)];
For some reason I can't pass any parameters to the function 'Function'. I have spend a lot of time looking into this but the the only solution i have found uses object ids and i would rather not get into that. This button is in a loop so i can't just have another function called thats get parameters from elsewhere.

+ (id)itemWithNormalImage:selectedImage:target:selector: does not support selectors with parameters. If you want to perform a selector that takes arguments, you can use + (id)itemWithNormalImage:selectedImage:block: instead. In the block just run any code you want:
__weak typeof(self) weakSelf = self;
[CCMenuItemImage itemWithNormalImage:#"Buy.jpg" selectedImage:#"Buy.jpg" block:^(id sender) {
[weakSelf methodWithParameterOne:one two:two];
}];

You can not send parameters by selectors by colons.
The typical example is :
[self performSelector:#selector(myMethodWithObject:) withObject:myObject];
Which calls
- (void)myMethodWithObject:(id)object;
Similarly you need to do like above, may be as :
CCMenuItemImage *BuyButton = [CCMenuItemImage itemWithNormalImage:#"Buy.jpg" selectedImage:#"Buy.jpg" target:self selector:#selector(Function:) withObject:cnt];
You need to change the Function by:
-(void)FunctionWithCnt:(<type>)cntObject;
and then use #selector(FunctionWithCnt:)

Related

Achieve method with passed variables in #selector

Where this:
distanceTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(applyShot:newPositionOne.x with:newPositionOne.y) userInfo:nil repeats:NO];
^^ This doesn't work.
Must equal this
[self applyShot:newPositionOne.x with:newPositionOne.y];
I basically need a delay before running this method, and it passes the variables because they'll be different by the time the method runs, so it has to remember them somehow.
However, I cannot for the life of me figure out how to pass variables in an #selector.
I've done it before with button.tag for example, but never for this.
Any help will be appreciated, thank you.
I understand I can just set global variables, but is is possible to pass them?
Ok so there a couple things that you can do. You are correct, it is difficult to pass variables into an #selector() declaration. Here are two options:
1) call a different method (defined in your class) that handles the variables. For example:
distanceTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(applyShot) userInfo:nil repeats:NO];
- (void)applyShot
{
// manipulate variables and do your logic here
}
2) Pass a dictionary into the userInfo argument of:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
This dictionary can look like this:
NSDictionary *params = #{#"newXPosition: #(90), #""newYPosition: #(100)"};
Then just change your applyShot: method to take in an NSDictionary argument, and then parse through the dictionary there to find the relevant values and "apply the shot."
A selector is not a packaged method. A selector is just the name of a message. You can think of it as a string (it used to be implemented as a string).
The traditional way to address this is with an NSInvocation which packages up a complete method call, not just the name of the message. That's well covered by Arguments in #selector. The other way to handle it is to package your options into the userInfo and change your method to read its parameters from that.
But typically the better solution today is to use dispatch_after instead:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^(void){
[self applyShot:newPositionOne.x with:newPositionOne.y];
});

How does (id)sender work in cocos2d?

The following code works good for me:
In the init method of a menu layer:
CCMenuItemFont *item1 = [CCMenuItemFont itemWithString:#"Level 1" target: self selector: #selector(startLevel:)];
item1.userData = (__bridge void*) ([NSNumber numberWithInt:1]);
...//create menu and add in the item1
-(void)startLevel: (CCMenuItem *)sender
{
NSNumber *number = sender.userData;
...
}
My questions are:
I didn't pass item1 when call the method startLevel: how does it know that the sender is item1?
is it written into selector? or is it written in cocoa?
CCMenuItem passes itself as parameter to this selector. Details are in CCMenuItem source code.
Regarding omitting passing itself as a parameter, do you mean like...
- (void) pushedStart : (id) sender
{
//start game
}
but you can't do
[self pushedStart];
because it needs a parameter? If so, what you can do this:
id junkID;
[self pushedStart: junkID];
JunkID will initialize to whatever the hell it is an unassigned ID assigns to, so you pass it as a reference and just don't use it for anything, which is good if you want to have a "start game" button but have the game automatically start inside of a timer or whatever else you're doing with your buttons
As a side note, and getting more into the guts of cocoa, the way it KNOWS (and what YOU must not forget) is that colon. When you call a function you put the variable after a colon [self eat: food];
When you put together the menu item you set it up with target:self, which makes the button use itself (not the layer "self" you use when you call [self eatABanana]) as a target. The button push of
menuButton = target:self selector:#selector(pushButton:)
is represented like
[self pushButton:menuButton]
If you forgot that colon, it's the same as calling a function and not passing the variable, which doesn't give a very helpful error message insofar as it doesn't help you locate where the problem is occurring. I've spent hours upon hours chasing down memory crashes resulting from writing #selector(startGame) instead of #selector(startGame:) in those damn menu buttons. I always feel stupid when I finally figure it out.

Using a block object instead of a selector?

I have:
[self schedule:#selector(tickhealth)];
And tickHealth method only has one line of code:
-(void)tickHealth
{
[hm decreaseBars:0.5];
}
is it possible to use block objects in place of a selector. for example something like:
[self schedule:^{
[hm decreaseBars:0.5];
}];
As Caleb & bbum correctly pointed out you cannot simply pass a block to your existing (and unchanged) - (void)schedule:(SEL)selector; method.
You can however do this:
Define block type:
typedef void(^ScheduleBlock)();
Change schedule: method to be defined similar to this:
- (void)schedule:(ScheduleBlock)block {
//blocks get created on the stack, thus we need to declare ownership explicitly:
ScheduleBlock myBlock = [[block copy] autorelease];
//...
myBlock();
}
Then call it like this:
[self schedule:^{
[hm decreaseBars:0.5];
}];
Further Objective-C block goodness compiled by Mike Ash that will get you kickstarted with blocks:
http://mikeash.com/pyblog/friday-qa-2008-12-26.html
http://mikeash.com/pyblog//friday-qa-2009-08-14-practical-blocks.html
http://mikeash.com/pyblog/friday-qa-2011-06-03-objective-c-blocks-vs-c0x-lambdas-fight.html
You can't just pass a block in place of a selector because those two things have different types. However, if you have control over the -schedule: method, you can easily modify it to accept and use a block in place of a selector.

Warning about making pointer from integer without a cast -- explanation needed

I have this code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic
NSLog(#"didSelectRowAtIndexPath");
//The hud will dispable all input on the view
HUD = [[MBProgressHUD alloc] initWithView:self.view];
// Add HUD to screen
[self.view addSubview:HUD];
// Regisete for HUD callbacks so we can remove it from the window at the right time
HUD.delegate = self;
HUD.labelText = #"Loading Events, Please Wait..";
int i = indexPath.row;
//Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:#selector(loadData:) onTarget:self withObject:i animated:YES];
}
And I get this warning:
warning: passing argument 3 of 'showWhileExecuting:onTarget:withObject:animated:' makes pointer from integer without a cast
Can somebody please explain what I'm doing wrong here? Could someone also briefly explain the situation with ints in Objective-C, coming from Java I find it odd that they are so confusing to use.
The problem is that showWhileExecuting:onTarget:withObject:animated: takes an object as its third argument. To get aroung this, you can wrap integers as objects using the NSNumber class
[NSNumber numberWithInt:i]
You will then have to unwrap the argument in the loadData: method by calling
[argument intValue]
The method takes an object as a third argument (withObject), but you passed an int instead.
Apparently, you provided an integer(int i) instead of an object pointer(type of id). It is not safe. Use NSNumber instead.
int i;
...
NSNumber * numberI = [NSNumber numberWithInt:i];
[HUD showWhileExecuting:#selector(loadData:) onTarget:self withObject:i animated:YES];
All of the answers above are the "correct" ones. I.e. be a good boy and use and NSNumber to pass the value.
However, … the following will work
"damn you, compiler, i'm smarter than you are:"
(cast your integer, totally not a valid object, to id)
[HUD showWhileExecuting:#selector(loadData:)
onTarget:self
withObject:(id)i
animated:YES];
i'm guessing (you didn't say), that your load data method looked like this:
- (void)loadData:(int)i { …
you will see code like this, which is the only reason i mentioned it.
you should be familiar with it.
someone thinks that saving 1 object allocation is going to make their code efficient; don't sweat object allocations, and wrap it up in an NSNumber as shown above
most C compilers will handle this correctly, but it's not guaranteed

Proper way of passing a primitive argument with an NSTimer

I'm using a basic timer that calls this method:
- (void) refresh:(id)obj
{
if (obj == YES) doSomething;
}
I want to call this method from certain areas of my code and also from a timer
[NSTimer scheduledTimerWithTimeInterval:refreshInterval
target:self
selector:#selector(refresh:)
userInfo:nil
repeats:YES];
When I put YES as the argument for the userInfo parameter, I get an EXC_BAD_ACCESS error; why is this?
Can someone help me do this the right way so that there is no ugly casting and such?
The userInfo parameter must be an object; it is typed id. YES is a primitive, namely the value 1. In order to make sure the userInfo object does not get deallocated, the timer retains it. So, when you passed YES, NSTimer was doing [(id)YES retain]. Try that in your own code and see what happens. :-P
As the Documentation states, the selector you give the method must have the signature
- (void)timerFireMethod:(NSTimer*)theTimer
This means you can't have an NSTimer invoke just any method—not directly at least. You can make a special method with the above signature which in turn invokes any method you want though.
So, say you have a method called refresh:, and you want to call it every so often, passing it YES. You can do this like so:
// somewhere
{
[NSTimer scheduledTimerWithTimeInterval:refreshInterval
target:self
selector:#selector(invokeRefresh:)
userInfo:nil
repeats:YES];
}
- (void)invokeRefresh:(NSTimer *)timer {
[self refresh:YES];
}
- (void)refresh:(BOOL)flag {
if (flag) {
// do something
}
}
In Objective-C, a primitive type is not an object. So you can't directly pass it to an argument which expects an id, which stands for a generic object. You need to wrap it into an NSNumber object.
Use
NSTimer*timer=[NSTimer scheduledTimerWithTimeInterval:refreshInterval
target:self
selector:#selector(refresh:)
userInfo:[NSNumber numberWithBool:YES]
repeats:YES];
and
- (void) refresh:(NSTimer*)timer
{
NSNumber* shouldDoSomething=[timer userInfo];
if ([shouldDoSomething boolValue]) doSomething;
}
Don't forget to invalidate and release the timer once it's done.
By the way, you don't have to compare a BOOL (or C++ bool) against YES or true or whatever. When you write
if(a>b) { ... }
a>b evaluates to a bool, and if uses the result. What you're doing there is like
if((a>b)==YES) { ... }
which is quite strange to me. It's not that the (..) after if should contain a comparison; it should contain a bool.
As a follow-up to kperryua's answer, if you want to pass a primitive through userInfo you can box it with NSNumber or NSValue; in the case of a boolean, you'd want to use [NSNumber numberWithBool:YES], then call boolValue in the timer callback to get back the primitive.